<?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>VCF Automation on The Nested Lab</title>
    <link>https://thenestedlab.com/products/vcf-automation/</link>
    <description>Recent content in VCF Automation on The Nested Lab</description>
    <generator>Hugo</generator>
    <language>en-gb</language>
    <lastBuildDate>Thu, 01 Oct 2026 00:00:00 +0100</lastBuildDate>
    <atom:link href="https://thenestedlab.com/products/vcf-automation/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Every resource in the VCF Automation 9.1 blueprint designer: the complete guide</title>
      <link>https://thenestedlab.com/posts/vcfa-blueprint-resource-reference/</link>
      <pubDate>Thu, 01 Oct 2026 00:00:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/vcfa-blueprint-resource-reference/</guid>
      <description>A complete guide to the VCF Automation 9.1 blueprint designer: every palette item, the YAML behind it, every field the platform accepts, recipes and traps. Every snippet validated, dry-run or deployed.</description>
      <content:encoded><![CDATA[<p>The blueprint designer in a VCF Automation 9.1 All Apps organization offers
sixteen items in three groups. Drag one onto the canvas and you get a few
lines of YAML. What you may write underneath them is spread across the VM
Service, VKS, NSX VPC and Automation guides. For some items, it&rsquo;s written
down nowhere at all.</p>
<p>This guide puts it in one place. For each item, you get:</p>
<ul>
<li>what it creates, and the YAML behind it;</li>
<li>every field the platform accepts;</li>
<li>a minimal snippet, and recipes for the common jobs;</li>
<li>the status fields worth reading;</li>
<li>the traps we hit.</li>
</ul>
<p>Three things make it more than a list from memory, which, given my memory,
is just as well:</p>
<ul>
<li><strong>The field lists come from the platform.</strong> VCF Automation publishes the
schema of every blueprint resource type through its API. The designer
carries the schema of every palette item. The Supervisor publishes the
definition of every Kubernetes kind it serves, and VCF Automation&rsquo;s VPC API
publishes its own. Where they disagree (and they do), the tables follow
what the platform enforces.</li>
<li><strong>Every snippet was checked.</strong> Each one went through VCF Automation&rsquo;s
validation API, and every Kubernetes manifest through a server-side dry
run on the Supervisor. Five test blueprints (all of them in the downloads)
deployed every item for real. The remaining recipes are ones our lab
catalog deploys every day.</li>
<li><strong>The traps are real.</strong> Several of the most useful lines in this guide come
from things that went wrong while testing: a NAT rule that ignored its
port, a firewall rule that grew an &ldquo;Any&rdquo;, a request that waits for an
address that can never come.</li>
</ul>
<p>We checked it on our lab platform, f06:</p>
<ul>
<li>VCF Automation 9.1.0;</li>
<li>a Supervisor on Kubernetes 1.32.9, with the VM Operator API at <code>v1alpha5</code>;</li>
<li>VKS with ClusterClasses up to <code>builtin-generic-v3.6.0</code>, and Kubernetes
releases up to 1.35.5;</li>
<li>NSX VPCs.</li>
</ul>
<p>Names in the examples (<code>f06</code>, <code>nested-pod</code>, <code>vpc-student05</code>,
<code>vsan-default-storage-policy</code>) are ours; use yours.</p>
<p>In the field tables, <strong>Values</strong> gives a field&rsquo;s choices and default. Where the
schema has neither, it gives an example, marked <em>e.g.</em>: the value from our
tested blueprints wherever one of them set the field, otherwise a typical one.
For an object or a list, the example is a short YAML flow value built from its
fields (<code>...</code> marks the ones left out). And <code>&lt;...&gt;</code> stands for a name of yours.</p>
<h2 id="the-shape-of-a-blueprint">The shape of a blueprint</h2>
<p>An All Apps blueprint is YAML with <code>formatVersion: 2</code>, its inputs, its
resources and its outputs:</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">formatVersion</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><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">vmName</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">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">VM name</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">default</span><span class="p">:</span><span class="w"> </span><span class="l">web-01</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">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">size</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">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Size</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">default</span><span class="p">:</span><span class="w"> </span><span class="l">small</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">oneOf</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="nt">Small, const</span><span class="p">:</span><span class="w"> </span><span class="l">small}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="nt">Medium, const</span><span class="p">:</span><span class="w"> </span><span class="l">medium}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><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">name</span><span class="p">:</span><span class="w"> </span><span class="l">team-a-dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">existing</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">vm1</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">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">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.namespace.id}</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">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">vmoperator.vmware.com/v1alpha5</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></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">metadata</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">${input.vmName}</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">className</span><span class="p">:</span><span class="w"> </span><span class="l">${&#39;best-effort-&#39; + input.size}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">imageName</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-24.04-server-cloudimg-amd64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">storageClass</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="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">vmName</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">${resource.vm1.object.metadata.name}</span><span class="w">
</span></span></span></code></pre></div><p>What the expressions can read:</p>
<table>
	<thead>
			<tr>
					<th>Expression</th>
					<th>Value</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>${input.&lt;name&gt;}</code></td>
					<td>A request input. An input group from a property group is <code>${input.&lt;group&gt;.&lt;property&gt;}</code>.</td>
			</tr>
			<tr>
					<td><code>${resource.&lt;name&gt;.&lt;property&gt;}</code></td>
					<td>Another resource&rsquo;s property; this also orders the two. <code>object</code> holds the live Kubernetes object of a Supervisor Resource.</td>
			</tr>
			<tr>
					<td><code>${propgroup.&lt;group&gt;.&lt;property&gt;}</code></td>
					<td>A constant property group&rsquo;s value.</td>
			</tr>
			<tr>
					<td><code>${secret.&lt;name&gt;}</code></td>
					<td>A VCF Automation secret, from the organization or the project.</td>
			</tr>
			<tr>
					<td><code>${env.deploymentName}</code>, <code>${count.index}</code></td>
					<td>The deployment&rsquo;s name; the instance number in a counted resource.</td>
			</tr>
			<tr>
					<td><code>${to_k8s_name(env.deploymentName, 63)}</code></td>
					<td>A string made safe for a Kubernetes name, as Broadcom&rsquo;s own samples use for <code>generateName</code>.</td>
			</tr>
	</tbody>
</table>
<p>Besides <code>type</code> and <code>properties</code>, each resource has <code>dependsOn</code> (an explicit
order) and <code>allocatePerInstance</code> (see <code>count</code> below). <code>formatVersion: 2</code> also
allows <code>metadata</code>, <code>variables</code>, and an output named <code>__deploymentOverview</code>,
whose Markdown value becomes the deployment&rsquo;s overview page.</p>
<h2 id="how-the-palette-maps-to-yaml">How the palette maps to YAML</h2>
<p>Behind the sixteen items there are only five resource types, and one of them
isn&rsquo;t in the palette. Most items are a type with part of its YAML already
filled in. For the workload items, that&rsquo;s a Kubernetes <code>apiVersion</code> and
<code>kind</code>; for the VPC items, it&rsquo;s a VPC configuration <code>kind</code>.</p>
<table>
	<thead>
			<tr>
					<th>Palette item</th>
					<th>YAML <code>type</code></th>
					<th>Pre-filled</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Supervisor Namespace</td>
					<td><code>CCI.Supervisor.Namespace</code></td>
					<td></td>
			</tr>
			<tr>
					<td><strong>VPC group</strong></td>
					<td></td>
					<td></td>
			</tr>
			<tr>
					<td>VPC</td>
					<td><code>CCI.VPC</code></td>
					<td></td>
			</tr>
			<tr>
					<td>VPC Configuration</td>
					<td><code>CCI.VPC.Configuration</code></td>
					<td>nothing: you set <code>kind</code></td>
			</tr>
			<tr>
					<td>Attachment</td>
					<td><code>CCI.VPC.Configuration</code></td>
					<td><code>kind: VPCAttachment</code></td>
			</tr>
			<tr>
					<td>IP Address Allocation</td>
					<td><code>CCI.VPC.Configuration</code></td>
					<td><code>kind: VPCIPAddressAllocation</code></td>
			</tr>
			<tr>
					<td>NAT Rule</td>
					<td><code>CCI.VPC.Configuration</code></td>
					<td><code>kind: VPCNATRule</code></td>
			</tr>
			<tr>
					<td>Group</td>
					<td><code>CCI.VPC.Configuration</code></td>
					<td><code>kind: VPCNetworkSecurityGroup</code></td>
			</tr>
			<tr>
					<td>Gateway Firewall Policy</td>
					<td><code>CCI.VPC.Configuration</code></td>
					<td><code>kind: VPCGatewayFirewallPolicy</code></td>
			</tr>
			<tr>
					<td><strong>Workload group</strong></td>
					<td></td>
					<td></td>
			</tr>
			<tr>
					<td>Supervisor Resource</td>
					<td><code>CCI.Supervisor.Resource</code></td>
					<td>nothing: any manifest</td>
			</tr>
			<tr>
					<td>Virtual Machine</td>
					<td><code>CCI.Supervisor.Resource</code></td>
					<td><code>vmoperator.vmware.com/v1alpha5</code> <code>VirtualMachine</code></td>
			</tr>
			<tr>
					<td>Virtual Machine Group</td>
					<td><code>CCI.Supervisor.Resource</code></td>
					<td><code>vmoperator.vmware.com/v1alpha5</code> <code>VirtualMachineGroup</code></td>
			</tr>
			<tr>
					<td>Virtual Machine Service</td>
					<td><code>CCI.Supervisor.Resource</code></td>
					<td><code>vmoperator.vmware.com/v1alpha3</code> <code>VirtualMachineService</code></td>
			</tr>
			<tr>
					<td>Subnet</td>
					<td><code>CCI.Supervisor.Resource</code></td>
					<td><code>crd.nsx.vmware.com/v1alpha1</code> <code>Subnet</code></td>
			</tr>
			<tr>
					<td>Persistent Volume Claim</td>
					<td><code>CCI.Supervisor.Resource</code></td>
					<td><code>v1</code> <code>PersistentVolumeClaim</code></td>
			</tr>
			<tr>
					<td>Secret</td>
					<td><code>CCI.Supervisor.Resource</code></td>
					<td><code>v1</code> <code>Secret</code></td>
			</tr>
			<tr>
					<td>Kubernetes Cluster</td>
					<td><code>CCI.Supervisor.Resource</code></td>
					<td><code>cluster.x-k8s.io/v1beta1</code> <code>Cluster</code></td>
			</tr>
			<tr>
					<td><em>(not in the palette)</em></td>
					<td><code>Util.PasswordEntry</code></td>
					<td></td>
			</tr>
	</tbody>
</table>
<p>Four consequences, worth knowing before any of the detail:</p>
<ul>
<li><strong>Anything the Supervisor understands can go in a blueprint.</strong> The workload
items are shortcuts. The generic Supervisor Resource takes any manifest the
namespace accepts. Our lab blueprints rely on a kind the palette doesn&rsquo;t
offer, <code>SubnetConnectionBindingMap</code>, to carry VLANs.</li>
<li><strong>The VPC items are a closed list.</strong> <code>CCI.VPC.Configuration</code> takes exactly
the five kinds above. A VPC&rsquo;s load balancer is a sixth kind in VCF
Automation&rsquo;s VPC API, and a blueprint can&rsquo;t make one (see
<a href="#vpc">VPC</a>).</li>
<li><strong>VCF Automation validates the outside, the platform the inside.</strong> The
validation API checks the resource type&rsquo;s own properties: required fields,
patterns, the namespace&rsquo;s two shapes. It doesn&rsquo;t look inside a <code>manifest</code>
or a <code>configs[].spec</code>: in our test, <code>powerState: Sideways</code> passed
validation. Those are checked when the request runs.</li>
<li><strong>The designer&rsquo;s schemas are not the platform&rsquo;s.</strong> The palette&rsquo;s forms
come from schemas bundled with VCF Automation, while the Supervisor and
the VPC API check against their own. They disagree in a handful of
places, listed next.</li>
</ul>
<h2 id="where-the-designer-and-the-platform-disagree">Where the designer and the platform disagree</h2>
<p>We compared each palette item&rsquo;s schema with the platform&rsquo;s definition of the
same <code>apiVersion</code> and <code>kind</code>. We went field by field (every bit as gripping
as it sounds) and tested every difference:</p>
<table>
	<thead>
			<tr>
					<th>Item</th>
					<th>The designer offers</th>
					<th>What the platform does</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Virtual Machine</td>
					<td><code>affinity.zoneAffinity</code>, <code>affinity.zoneAntiAffinity</code>, and VM affinity terms named <code>...IgnoredDuringExecution</code></td>
					<td>Rejects them as unknown fields. VM affinity and anti-affinity take <code>requiredDuringSchedulingPreferredDuringExecution</code> and <code>preferredDuringSchedulingPreferredDuringExecution</code>.</td>
			</tr>
			<tr>
					<td>Virtual Machine</td>
					<td>no <code>linuxPrep.password</code>, <code>linuxPrep.scriptText</code>, <code>crypto.vTPMMode</code>, <code>currentSnapshotName</code>, or disk options at volume level</td>
					<td>Accepts all of them.</td>
			</tr>
			<tr>
					<td>Virtual Machine</td>
					<td><code>bootOptions.firmware</code> as <code>BIOS</code> or <code>EFI</code>, and <code>bootOptions.enterBootSetup</code></td>
					<td><code>spec.bootOptions.firmware: Unsupported value: &quot;BIOS&quot;: supported values: &quot;bios&quot;, &quot;efi&quot;</code>, and <code>strict decoding error: unknown field &quot;spec.bootOptions.enterBootSetup&quot;</code>.</td>
			</tr>
			<tr>
					<td>Virtual Machine <code>v1alpha4</code>, <code>v1alpha3</code></td>
					<td><code>network.nameServers</code></td>
					<td>The field is <code>nameservers</code>, lower case.</td>
			</tr>
			<tr>
					<td>Subnet</td>
					<td><code>regionName</code>, <code>ipBlockNames</code>, <code>description</code></td>
					<td>Rejects them. Accepts <code>vlanConnectionName</code>, which the designer doesn&rsquo;t show.</td>
			</tr>
			<tr>
					<td>Persistent Volume Claim</td>
					<td><code>accessMode</code></td>
					<td><code>strict decoding error: unknown field &quot;spec.accessMode&quot;</code>. The field is <code>accessModes</code>.</td>
			</tr>
			<tr>
					<td>Kubernetes Cluster</td>
					<td><code>cluster.x-k8s.io/v1beta1</code></td>
					<td>Serves it, with <code>cluster.x-k8s.io/v1beta1 Cluster is deprecated; use cluster.x-k8s.io/v1beta2 Cluster</code>.</td>
			</tr>
			<tr>
					<td>Group</td>
					<td><code>vmSelectors[].selector</code>, <code>podSelectors[].selector</code></td>
					<td><code>spec.vmSelectors[0]: Required value: must specify at least one selector</code>. The field is <code>labelSelector</code>; the API also offers <code>propertySelector</code>.</td>
			</tr>
			<tr>
					<td>Gateway Firewall Policy</td>
					<td><code>ruleCount</code></td>
					<td>Computed by the API; not accepted. A rule&rsquo;s <code>from</code> is required, though the schema says empty means any.</td>
			</tr>
	</tbody>
</table>
<p>None of this stops the designer from saving a blueprint. It surfaces when the
request runs.</p>
<h2 id="what-every-resource-shares">What every resource shares</h2>
<h3 id="properties-on-every-type">Properties on every type</h3>
<table>
	<thead>
			<tr>
					<th>Property</th>
					<th>On</th>
					<th>What it does</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>count</code></td>
					<td>all</td>
					<td>How many instances to create. Default 1.</td>
			</tr>
			<tr>
					<td><code>allocatePerInstance</code></td>
					<td>all (beside <code>type</code>)</td>
					<td>Required for <code>${count.index}</code>: without it the validator refuses the blueprint with <code>should have allocatePerInstance property set to True</code>.</td>
			</tr>
			<tr>
					<td><code>dependsOn</code></td>
					<td>all (beside <code>type</code>)</td>
					<td>Explicit order. A reference to another resource orders the two already.</td>
			</tr>
			<tr>
					<td><code>context</code></td>
					<td>Supervisor Resource items</td>
					<td><strong>Required.</strong> The namespace the manifest goes into: <code>${resource.&lt;namespace&gt;.id}</code>.</td>
			</tr>
			<tr>
					<td><code>existing</code></td>
					<td>Namespace, Supervisor Resource items</td>
					<td><code>true</code> adopts what already exists instead of creating it.</td>
			</tr>
			<tr>
					<td><code>wait</code></td>
					<td>Supervisor Resource items, VPC Configuration</td>
					<td>When the resource counts as done.</td>
			</tr>
	</tbody>
</table>
<p>Recipe, two Secrets from one 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="w">  </span><span class="nt">sec</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">allocatePerInstance</span><span class="p">:</span><span class="w"> </span><span class="kc">true</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">count</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.ns.id}</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">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</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">Secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">metadata</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">${&#39;ref-s-&#39; + count.index}</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">Opaque</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">stringData</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">note</span><span class="p">:</span><span class="w"> </span><span class="l">created by instance ${count.index}</span><span class="w">
</span></span></span></code></pre></div><p>The deployment then holds <code>sec[0]</code> and <code>sec[1]</code>, and the namespace
<code>ref-s-0</code> and <code>ref-s-1</code>.</p>
<h3 id="wait-when-a-resource-is-finished"><code>wait</code>: when a resource is finished</h3>
<p>Without a <code>wait</code>, a Supervisor Resource is finished as soon as the Supervisor
accepts the manifest. That&rsquo;s fine for a Secret, and wrong for a VM whose
address an output needs. <code>wait</code> takes conditions, fields, or both:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><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">conditions</span><span class="p">:</span><span class="w">                      </span><span class="c"># status.conditions[] of the object</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">Ready</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">status</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;True&#34;</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">Ready</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">status</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">reason</span><span class="p">:</span><span class="w"> </span><span class="l">Failed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">indicatesFailure</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">       </span><span class="c"># this one fails the resource instead of finishing it</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="c"># any field of the object, by path</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">status.powerState</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">PoweredOn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">skipWaitOnDelete</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">          </span><span class="c"># true: deletion does not wait for the object to be gone</span><span class="w">
</span></span></span></code></pre></div><p>A Supervisor Resource can also watch log output with <code>executionLogs</code>:
<code>progressMessagePattern</code> while a matching message appears, and
<code>failureMessagePattern</code> to fail the resource.</p>
<p>The designer pre-fills a <code>wait</code> for some items:</p>
<table>
	<thead>
			<tr>
					<th>Item</th>
					<th>Designer&rsquo;s default <code>wait</code></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Virtual Machine</td>
					<td>condition <code>VirtualMachineCreated</code> = <code>True</code></td>
			</tr>
			<tr>
					<td>Virtual Machine Group</td>
					<td>condition <code>Ready</code> = <code>True</code></td>
			</tr>
			<tr>
					<td>NAT Rule, Group</td>
					<td>condition <code>Realized</code> = <code>True</code></td>
			</tr>
			<tr>
					<td>everything else</td>
					<td>none</td>
			</tr>
	</tbody>
</table>
<p>The VM&rsquo;s default came from a 9.0 problem: VM status could come back empty,
and Broadcom&rsquo;s KB 435137 gave this <code>wait</code> as the workaround. That condition
is true before the VM is even on, let alone has an address.</p>
<p>VCF Automation also waits by itself in one place: <strong>a Virtual Machine Service
of type <code>LoadBalancer</code> is not finished until it has an external address</strong>,
with or without a <code>wait</code>. In a VPC without a load balancer, that address
never comes, and the request stays in progress until it times out. Ours was
still <code>PARTIAL</code> after ten minutes, with the service <code>&lt;pending&gt;</code> on the
Supervisor.</p>
<h3 id="reading-a-resources-live-state">Reading a resource&rsquo;s live state</h3>
<p>Every Supervisor Resource exposes the object as the Supervisor sees it under
<code>object</code>, and a VPC Configuration exposes its objects under <code>configs</code>:</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">vmIp</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">${resource.vm1.object.status.network.primaryIP4}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">vmHost</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">${resource.vm1.object.status.nodeName}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">lbIp</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">${resource.webLb.object.status.loadBalancer.ingress[0].ip}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">publicIp</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">${resource.publicIp.configs[0].spec.allocationIPs}</span><span class="w">
</span></span></span></code></pre></div><p><strong>Outputs are computed once, when the request finishes</strong>, and not refreshed
afterwards. A VM that waited only for <code>PoweredOn</code> finished before its guest
reported an address, and its IP output stayed empty for good. Waiting on the
condition that marks the guest&rsquo;s network as configured fixes it. This one
gave the output <code>172.30.0.2</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><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">conditions</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">VirtualMachineGuestNetworkConfigSynced</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">status</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;True&#34;</span><span class="w">
</span></span></span></code></pre></div><h3 id="checking-a-manifest-before-you-deploy-it">Checking a manifest before you deploy it</h3>
<p>The fastest check we found is a server-side dry run with strict field
validation, against the namespace the blueprint will use:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">kubectl apply --dry-run=server --validate=strict -n &lt;namespace&gt; -f vm.yaml
</span></span></code></pre></div><p>It runs the Supervisor&rsquo;s schema checks and admission webhooks, flags unknown
fields, and creates nothing. It works for every workload kind. It does <strong>not</strong>
work for the VPC kinds: VCF Automation&rsquo;s VPC API ignores <code>dryRun</code> and creates
the object, as one of our probes found.</p>
<h2 id="supervisor-namespace">Supervisor Namespace</h2>
<p><code>type: CCI.Supervisor.Namespace</code>. Creates a vSphere Namespace through VCF
Automation, inside the project&rsquo;s allocation, or adopts one that exists.
Everything in the Workload group needs one: their <code>context</code> points at it.</p>
<p>The schema has two shapes, and the validator enforces them: an existing
namespace takes <code>name</code> and <code>existing: true</code> and nothing else; a new one needs
<code>generateName</code>, <code>className</code>, <code>regionName</code> and <code>vpcName</code>.</p>
<table>
	<thead>
			<tr>
					<th>Property</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>generateName</code></td>
					<td><strong>New namespace.</strong> Prefix; VCF Automation adds <code>-</code> and five random characters (<code>ns-ref-bstk7</code>). Must match <code>^[a-z0-9]([-a-z0-9]*[a-z0-9])?$</code>, so it cannot end in a hyphen.</td>
			</tr>
			<tr>
					<td><code>className</code></td>
					<td><strong>New namespace.</strong> The namespace class: limits, VM classes, storage classes and content libraries.</td>
			</tr>
			<tr>
					<td><code>regionName</code></td>
					<td><strong>New namespace.</strong> The region.</td>
			</tr>
			<tr>
					<td><code>vpcName</code></td>
					<td><strong>New namespace.</strong> The VPC its workloads use: an existing VPC&rsquo;s name, or <code>${resource.&lt;vpc&gt;.name}</code>.</td>
			</tr>
			<tr>
					<td><code>name</code> + <code>existing: true</code></td>
					<td><strong>Existing namespace.</strong> <code>name</code> alone matches neither shape.</td>
			</tr>
			<tr>
					<td><code>zones[]</code></td>
					<td>Per vSphere Zone: <code>name</code>, <code>cpuLimit</code>, <code>cpuReservation</code>, <code>memoryLimit</code>, <code>memoryReservation</code>, all five required.</td>
			</tr>
			<tr>
					<td><code>storageClasses[]</code></td>
					<td><code>name</code> (the storage policy as the namespace names it) and <code>limit</code>.</td>
			</tr>
			<tr>
					<td><code>vmClasses[]</code>, <code>contentSources[]</code></td>
					<td>VM classes and image sources beyond the class&rsquo;s own.</td>
			</tr>
			<tr>
					<td><code>sharedSubnetNames[]</code>, <code>infraPolicyNames[]</code>, <code>segName</code>, <code>description</code></td>
					<td>Shared subnets, extra infrastructure policies, an Avi Service Engine Group, a description.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (25)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>name</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>team-a-dev</code></td>
					<td>Supervisor namespace name</td>
			</tr>
			<tr>
					<td><code>count</code></td>
					<td>integer</td>
					<td></td>
					<td>default <code>1</code></td>
					<td>The number of resource instances to be created.</td>
			</tr>
			<tr>
					<td><code>zones</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: domain-c9, cpuLimit: 4000M}]</code></td>
					<td>Zone overrides for the namespace</td>
			</tr>
			<tr>
					<td><code>zones[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>domain-c9</code></td>
					<td>Name of the zone</td>
			</tr>
			<tr>
					<td><code>zones[].cpuLimit</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>4000M</code></td>
					<td>CPU limit in M or G</td>
			</tr>
			<tr>
					<td><code>zones[].memoryLimit</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>8192Mi</code></td>
					<td>Memory limit in Mi, Gi, or Ti</td>
			</tr>
			<tr>
					<td><code>zones[].cpuReservation</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>0M</code></td>
					<td>CPU reservation in M or G</td>
			</tr>
			<tr>
					<td><code>zones[].memoryReservation</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>0Mi</code></td>
					<td>Memory reservation in Mi, Gi, or Ti</td>
			</tr>
			<tr>
					<td><code>segName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>Default-Group</code></td>
					<td>Name of the Service Engine Group</td>
			</tr>
			<tr>
					<td><code>vpcName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>vpc-student05</code></td>
					<td>Name of the vpc</td>
			</tr>
			<tr>
					<td><code>existing</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Use existing supervisor namespace</td>
			</tr>
			<tr>
					<td><code>className</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>nested-pod</code></td>
					<td>Name of the supervisor namespace class</td>
			</tr>
			<tr>
					<td><code>vmClasses</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: best-effort-small}]</code></td>
					<td>VM Class overrides for the namespace</td>
			</tr>
			<tr>
					<td><code>vmClasses[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>best-effort-small</code></td>
					<td>Name of the vm class</td>
			</tr>
			<tr>
					<td><code>regionName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>f06</code></td>
					<td>Name of the region</td>
			</tr>
			<tr>
					<td><code>description</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>Team sandbox</code></td>
					<td>Description of the supervisor namespace</td>
			</tr>
			<tr>
					<td><code>generateName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>ns-demo</code></td>
					<td>Supervisor namespace generateName</td>
			</tr>
			<tr>
					<td><code>contentSources</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: my-library, type: ContentLibrary}]</code></td>
					<td>Content Source overrides for the namespace</td>
			</tr>
			<tr>
					<td><code>contentSources[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>my-library</code></td>
					<td>Name of the content source</td>
			</tr>
			<tr>
					<td><code>contentSources[].type</code></td>
					<td>string</td>
					<td>yes</td>
					<td>default <code>ContentLibrary</code></td>
					<td>Type of the content source</td>
			</tr>
			<tr>
					<td><code>storageClasses</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: vSAN Default Storage Policy, limit: 100Gi}]</code></td>
					<td>Storage Class overrides for the namespace</td>
			</tr>
			<tr>
					<td><code>storageClasses[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>vSAN Default Storage Policy</code></td>
					<td>Name of the storage class</td>
			</tr>
			<tr>
					<td><code>storageClasses[].limit</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>100Gi</code></td>
					<td>Storage Class limit in Mi, Gi, or Ti</td>
			</tr>
			<tr>
					<td><code>infraPolicyNames</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[&lt;infrastructure policy&gt;]</code></td>
					<td>Non-mandatory Infra Policy names</td>
			</tr>
			<tr>
					<td><code>sharedSubnetNames</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[&lt;shared subnet&gt;]</code></td>
					<td>Name of subnets</td>
			</tr>
	</tbody>
</table>

</details></p>

<p>Minimal, a new namespace with the zone and storage our class doesn&rsquo;t set:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><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">ns-demo</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">nested-pod</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">vpc-student05</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></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">100Gi</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></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">4000M</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">cpuReservation</span><span class="p">:</span><span class="w"> </span><span class="l">0M</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">8192Mi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">memoryReservation</span><span class="p">:</span><span class="w"> </span><span class="l">0Mi</span><span class="w">
</span></span></span></code></pre></div><p><strong>Recipes</strong></p>
<ul>
<li>
<p><em>A quota sized from the request</em>, so a namespace never outgrows what was
asked for. Our lab blueprints compute the limits from the requested sizes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">    </span><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="s2">&#34;${input.hosts * 200 + &#39;Gi&#39;}&#34;</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></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="s2">&#34;${input.hosts * 8000 + &#39;M&#39;}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">cpuReservation</span><span class="p">:</span><span class="w"> </span><span class="l">0M</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="s2">&#34;${input.hosts * 32768 + &#39;Mi&#39;}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">memoryReservation</span><span class="p">:</span><span class="w"> </span><span class="l">0Mi</span><span class="w">
</span></span></span></code></pre></div></li>
<li>
<p><em>Deploy into a namespace that already exists</em>, for example one an
administrator prepared:</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">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">name</span><span class="p">:</span><span class="w"> </span><span class="l">team-a-dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">existing</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div></li>
</ul>
<p><strong>Status worth reading:</strong> <code>${resource.&lt;ns&gt;.id}</code> is
<code>cci:&lt;project&gt;:&lt;namespace&gt;</code>, which <code>context</code> takes.</p>
<p><strong>Gotchas</strong></p>
<ul>
<li>Whether <code>zones</code> and <code>storageClasses</code> are optional depends on the namespace
class. Ours sets neither, and VCF Automation refused the minimal shape in
turn: <code>Zone should be specified in Namespace or in Namespace class</code>, then
<code>Storage Class should be specified in Namespace or in Namespace class</code>.</li>
<li>The zone <code>name</code> is the vSphere Zone. A Supervisor without zones still has
one, named after its cluster (<code>domain-c9</code> on f06).</li>
<li>A VM can only use a VM class the namespace allows and an image from its
content sources. The validator cannot know either; the request finds out.</li>
</ul>
<h2 id="vpc">VPC</h2>
<p><code>type: CCI.VPC</code>. Creates an NSX VPC in a region. Most blueprints use an
existing VPC, through the namespace&rsquo;s <code>vpcName</code>. This item is for a
blueprint that brings its own network.</p>
<table>
	<thead>
			<tr>
					<th>Property</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>generateName</code></td>
					<td><strong>Required.</strong> VCF Automation names the VPC <code>&lt;generateName&gt;-&lt;project&gt;-&lt;5 characters&gt;</code>: <code>vpc-ref-default-project-y3698</code>.</td>
			</tr>
			<tr>
					<td><code>regionName</code></td>
					<td><strong>Required.</strong> The region.</td>
			</tr>
			<tr>
					<td><code>privateIPs[]</code></td>
					<td>The VPC&rsquo;s private CIDRs. Empty uses the project&rsquo;s default.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (4)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>count</code></td>
					<td>integer</td>
					<td></td>
					<td>default <code>1</code></td>
					<td>The number of resource instances to be created.</td>
			</tr>
			<tr>
					<td><code>privateIPs</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[10.200.0.0/20]</code></td>
					<td>List of private IPs to be used in the VPC</td>
			</tr>
			<tr>
					<td><code>regionName</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>f06</code></td>
					<td>Region name for the VPC</td>
			</tr>
			<tr>
					<td><code>generateName</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>vpc-app</code></td>
					<td>Prefix for the generated name of the VPC</td>
			</tr>
	</tbody>
</table>

</details></p>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">vpc</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.VPC</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">vpc-app</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">privateIPs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="m">10.200.0.0</span><span class="l">/20</span><span class="w">
</span></span></span></code></pre></div><p><strong>Status worth reading:</strong> <code>id</code> (<code>cci:vpc:&lt;project&gt;:&lt;name&gt;</code>), which every VPC
Configuration takes as <code>vpc</code>, and <code>name</code>, which a namespace takes as
<code>vpcName</code>.</p>
<p><strong>Gotchas</strong></p>
<ul>
<li>A new VPC reaches nothing outside itself until it has an
<a href="#attachment">Attachment</a> to a VPC connectivity profile; give the namespace
<code>dependsOn</code> on the attachment.</li>
<li><code>privateIPs</code> must not overlap the connectivity profile&rsquo;s Private Transit
Gateway blocks, and the error only comes at attachment time: <code>private IP CIDR 172.31.64.0/20 overlaps with private TGW IP block CIDR 172.31.0.0/16</code>.</li>
<li><strong>A blueprint cannot give its VPC a load balancer.</strong> In VCF Automation&rsquo;s
VPC API the load balancer is its own kind, <code>LoadBalancer</code>, and
<code>CCI.VPC.Configuration</code> refuses it: <code>Failed to match exactly one schema (matched 0 out of 5)</code>. Without one, a LoadBalancer service never gets an
address (and its request never finishes, see <a href="#wait-when-a-resource-is-finished"><code>wait</code></a>),
and Broadcom&rsquo;s VPC page warns that a VPC without load balancing cannot run
VKS. For either, use a VPC made in the UI with <strong>Enable load balancing</strong>
on, and name it in the namespace&rsquo;s <code>vpcName</code>.</li>
</ul>
<h2 id="vpc-configuration">VPC Configuration</h2>
<p><code>type: CCI.VPC.Configuration</code>. One item for every object that lives inside a
VPC, and <code>kind</code> chooses which. The five palette entries below are this item
with <code>kind</code> filled in. Each <code>configs[]</code> entry becomes one object, so one
resource can create several rules or groups of the same kind.</p>
<table>
	<thead>
			<tr>
					<th>Property</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>vpc</code></td>
					<td><strong>Required.</strong> The VPC&rsquo;s id: <code>${resource.vpc.id}</code>, or an existing VPC&rsquo;s <code>cci:vpc:&lt;project&gt;:&lt;name&gt;</code>.</td>
			</tr>
			<tr>
					<td><code>kind</code></td>
					<td><strong>Required.</strong> <code>VPCAttachment</code>, <code>VPCIPAddressAllocation</code>, <code>VPCNATRule</code>, <code>VPCNetworkSecurityGroup</code> or <code>VPCGatewayFirewallPolicy</code>, and nothing else.</td>
			</tr>
			<tr>
					<td><code>apiVersion</code></td>
					<td><code>vpc.nsx.vmware.com/v1alpha1</code>.</td>
			</tr>
			<tr>
					<td><code>configs[]</code></td>
					<td><strong>Required.</strong> Per object: <code>generateName</code> (<strong>required</strong>), <code>spec</code>, <code>labels</code>, <code>annotations</code>.</td>
			</tr>
			<tr>
					<td><code>wait</code></td>
					<td>Applies to every object in <code>configs</code>.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (20)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>vpc</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>${resource.vpc.id}</code></td>
					<td>ID of the parent VPC this resource is associated with.</td>
			</tr>
			<tr>
					<td><code>kind</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>VPCNATRule</code></td>
					<td>The kind of the resource (e.g., VPCNetworkSecurityGroup, VPCIPAddressAllocation, VPCNATRule, VPCAttachment).</td>
			</tr>
			<tr>
					<td><code>wait</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{fields: [{path: status.conditions[0].status, value: True}], ...}</code></td>
					<td>Wait conditions applied to all config resources. All resources must satisfy these conditions before the operation is considered complete.</td>
			</tr>
			<tr>
					<td><code>wait.fields</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{path: status.conditions[0].status, value: True}]</code></td>
					<td>List of field conditions to wait for.</td>
			</tr>
			<tr>
					<td><code>wait.fields[].path</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>status.conditions[0].status</code></td>
					<td>JSONPath to the field to check (e.g., status.phase).</td>
			</tr>
			<tr>
					<td><code>wait.fields[].value</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>True</code></td>
					<td>The expected value of the field. Use &lsquo;*&rsquo; for any non-null value.</td>
			</tr>
			<tr>
					<td><code>wait.fields[].indicatesFailure</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Whether this field condition indicates a failure state.</td>
			</tr>
			<tr>
					<td><code>wait.conditions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{type: Realized, reason: &lt;condition reason&gt;}]</code></td>
					<td>List of status conditions to wait for.</td>
			</tr>
			<tr>
					<td><code>wait.conditions[].type</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>Realized</code></td>
					<td>The type of the condition (e.g., Ready, Realized).</td>
			</tr>
			<tr>
					<td><code>wait.conditions[].reason</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&lt;condition reason&gt;</code></td>
					<td>Optional reason for the condition.</td>
			</tr>
			<tr>
					<td><code>wait.conditions[].status</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>True</code></td>
					<td>The expected status of the condition (e.g., True, False).</td>
			</tr>
			<tr>
					<td><code>wait.conditions[].indicatesFailure</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Whether this condition indicates a failure state.</td>
			</tr>
			<tr>
					<td><code>wait.skipWaitOnDelete</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Whether to skip waiting for conditions during delete operations.</td>
			</tr>
			<tr>
					<td><code>count</code></td>
					<td>integer</td>
					<td></td>
					<td>default <code>1</code></td>
					<td>The number of resource instances to be created.</td>
			</tr>
			<tr>
					<td><code>configs</code></td>
					<td>array of object</td>
					<td>yes</td>
					<td>e.g. <code>[{generateName: dnat-web, spec: {action: DNAT, translatedNetwork: 10.200.0.10}}]</code></td>
					<td>List of resources associated with the VPC.</td>
			</tr>
			<tr>
					<td><code>configs[].spec</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{action: DNAT, translatedNetwork: 10.200.0.10}</code></td>
					<td>The specification of the associated resource.</td>
			</tr>
			<tr>
					<td><code>configs[].labels</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{app: web}</code></td>
					<td>Labels for categorizing the resource.</td>
			</tr>
			<tr>
					<td><code>configs[].annotations</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{owner: team-a}</code></td>
					<td>Annotations for additional metadata about the resource.</td>
			</tr>
			<tr>
					<td><code>configs[].generateName</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>dnat-web</code></td>
					<td>A prefix for generating a unique name for the resource.</td>
			</tr>
			<tr>
					<td><code>apiVersion</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>vpc.nsx.vmware.com/v1alpha1</code></td>
					<td>The API version of the resource.</td>
			</tr>
	</tbody>
</table>

</details></p>

<p>Three things hold for all five kinds:</p>
<ul>
<li><strong><code>generateName</code> is the name, not a prefix.</strong> VCF Automation names the
object <code>&lt;vpc&gt;:&lt;generateName&gt;</code>, as written: our group was
<code>vpc-ref-default-project-y3698:web</code>. Keep it unique per kind in the VPC.</li>
<li><strong>VCF Automation fills in <code>spec.vpcName</code> and <code>spec.regionName</code></strong> from the
<code>vpc</code> property; leave them out.</li>
<li><strong>Another resource reads an object as <code>configs[n]</code></strong>:
<code>${resource.webGroup.configs[0].name}</code> is the full name a firewall rule
needs, and <code>${resource.publicIp.configs[0].spec.allocationIPs}</code> is the
address an allocation got.</li>
</ul>
<p>The shape, for every kind:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">natRules</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.VPC.Configuration</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">vpc</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.vpc.id}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">vpc.nsx.vmware.com/v1alpha1</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">VPCNATRule</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">configs</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">dnat-web</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 class="w"> </span><span class="l">... }</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">dnat-ssh</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 class="w"> </span><span class="l">... }</span><span class="w">
</span></span></span></code></pre></div><h3 id="attachment">Attachment</h3>
<p><code>kind: VPCAttachment</code>. Attaches the VPC to a VPC connectivity profile, which
decides its transit gateway, its external IP blocks and whether it gets a
default outbound NAT.</p>
<table>
	<thead>
			<tr>
					<th><code>spec</code> field</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>vpcConnectivityProfileName</code></td>
					<td><strong>Required.</strong> The profile, as NSX names it (f06&rsquo;s is <code>default--f06</code>).</td>
			</tr>
			<tr>
					<td><code>preferredDefaultSNATIP</code></td>
					<td>The address for the VPC&rsquo;s automatic SNAT. It must be free in the external block; empty lets NSX choose.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (2)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>spec.preferredDefaultSNATIP</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>192.168.144.30</code></td>
					<td>PreferredDefaultSNATIP specifies the translated IP for VPC auto SNAT rules. The specified IP must be available.</td>
			</tr>
			<tr>
					<td><code>spec.vpcConnectivityProfileName</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>default--f06</code></td>
					<td>VPCConnectivityProfileName specifies the name of the VPC Connectivity Profile associated with the VPC.</td>
			</tr>
	</tbody>
</table>

</details></p>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">attach</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.VPC.Configuration</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">vpc</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.vpc.id}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">vpc.nsx.vmware.com/v1alpha1</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">VPCAttachment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">configs</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">attach</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">vpcConnectivityProfileName</span><span class="p">:</span><span class="w"> </span><span class="l">default--f06</span><span class="w">
</span></span></span></code></pre></div><p>With the attachment realized, NSX adds the VPC&rsquo;s default SNAT rule and its
address by itself (ours: <code>10.200.0.0/20</code> to <code>192.168.144.14</code>).</p>
<h3 id="ip-address-allocation">IP Address Allocation</h3>
<p><code>kind: VPCIPAddressAllocation</code>. Reserves addresses from one of the VPC&rsquo;s IP
blocks, typically an external address for a NAT rule.</p>
<table>
	<thead>
			<tr>
					<th><code>spec</code> field</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>ipAddressBlockVisibility</code></td>
					<td><code>Private</code> (default), <code>PrivateTGW</code> or <code>External</code>. The NSX API&rsquo;s own default is <code>External</code>.</td>
			</tr>
			<tr>
					<td><code>allocationSize</code></td>
					<td>How many addresses, a power of 2. Either this or <code>allocationIPs</code>.</td>
			</tr>
			<tr>
					<td><code>allocationIPs</code></td>
					<td>Specific addresses, as a CIDR (<code>192.168.0.1/32</code>).</td>
			</tr>
			<tr>
					<td><code>ipBlockName</code></td>
					<td>A particular block, when the visibility has more than one.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (4)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>spec.allocationIPs</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>192.168.144.18</code></td>
					<td>The specific IP addresses from IPBlock that needs to be requested. If specified, it should be passed like 192.168.0.0/24 or 192.168.0.1/32.</td>
			</tr>
			<tr>
					<td><code>spec.allocationSize</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>1</code></td>
					<td>Allocation IP address size for auto allocating IPs from IPBlock. The IP addresses will be auto allocated from unused IP addresses based on allocation size.</td>
			</tr>
			<tr>
					<td><code>spec.ipAddressBlockVisibility</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>External</code></td>
					<td>Visibility of IP address block. Must be External, Private or PrivateTGW. Note: the default Private Visibility is different from NSX API&rsquo;s default External Visibility.</td>
			</tr>
			<tr>
					<td><code>spec.ipBlockName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>:f06-vpc-ext02</code></td>
					<td>IPBlock name for allocating IP address.</td>
			</tr>
	</tbody>
</table>

</details></p>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">publicIp</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.VPC.Configuration</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">attach]</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">vpc</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.vpc.id}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">vpc.nsx.vmware.com/v1alpha1</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">VPCIPAddressAllocation</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">configs</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">web-ip</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">ipAddressBlockVisibility</span><span class="p">:</span><span class="w"> </span><span class="l">External</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">allocationSize</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span></code></pre></div><p>The allocated address appears in the object&rsquo;s own spec:
<code>${resource.publicIp.configs[0].spec.allocationIPs}</code> gave <code>192.168.144.18</code>.
An external allocation needs the attachment first, hence the <code>dependsOn</code>.</p>
<h3 id="nat-rule">NAT Rule</h3>
<p><code>kind: VPCNATRule</code>. A NAT rule on the VPC&rsquo;s gateway. The designer&rsquo;s default
<code>wait</code> is <code>Realized</code>.</p>
<table>
	<thead>
			<tr>
					<th><code>spec</code> field</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>action</code></td>
					<td><strong>Required.</strong> <code>SNAT</code>, <code>DNAT</code>, <code>Reflexive</code>, <code>NoSNAT</code> or <code>NoDNAT</code>.</td>
			</tr>
			<tr>
					<td><code>translatedNetwork</code></td>
					<td><strong>Required.</strong> For SNAT, one address from the VPC&rsquo;s external block.</td>
			</tr>
			<tr>
					<td><code>sourceNetwork</code></td>
					<td>One address, a comma-separated list, or a CIDR. Mandatory for SNAT.</td>
			</tr>
			<tr>
					<td><code>destinationNetwork</code></td>
					<td>One address; empty means any.</td>
			</tr>
			<tr>
					<td><code>serviceEntry</code></td>
					<td><code>protocol</code> (<code>TCP</code>, <code>UDP</code>, <code>ICMP</code>), <code>sourcePorts</code>, <code>destinationPorts</code>, <code>translatedPorts</code>. See the warning.</td>
			</tr>
			<tr>
					<td><code>sequenceNumber</code></td>
					<td>Priority, default 0.</td>
			</tr>
			<tr>
					<td><code>firewallMatch</code></td>
					<td><code>MatchInternalAddress</code> (default), <code>MatchExternalAddress</code> or <code>ByPass</code>.</td>
			</tr>
			<tr>
					<td><code>enabled</code>, <code>logging</code></td>
					<td>Defaults <code>true</code> and <code>false</code>.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (13)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>spec.action</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>DNAT</code></td>
					<td>Action represents action of NAT Rule. Valid values: SNAT, DNAT, Reflexive, NoSNAT and NoDNAT.</td>
			</tr>
			<tr>
					<td><code>spec.destinationNetwork</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>192.168.144.18</code></td>
					<td>DestinationNetwork represents the destination network. The value can be a single IPv4 address or CIDR, or a comma separated list of IPv4 addresses.</td>
			</tr>
			<tr>
					<td><code>spec.enabled</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>NAT Rule enabled flag Enabled indicates whether the NAT rule is enabled or disabled. The default is True.</td>
			</tr>
			<tr>
					<td><code>spec.firewallMatch</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>MATCH_INTERNAL_ADDRESS</code></td>
					<td>FirewallMatch indicates how the firewall matches the address after NATing if firewall stage is not skipped.</td>
			</tr>
			<tr>
					<td><code>spec.logging</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>NAT Rule logging flag Logging indicates whether the logging of NAT rule is enabled or disabled. The default is False.</td>
			</tr>
			<tr>
					<td><code>spec.sequenceNumber</code></td>
					<td>integer</td>
					<td></td>
					<td>default <code>0</code></td>
					<td>SequenceNumber decides the priority of a NAT rule. Valid range is [0, 2147481599]. Default is 0.</td>
			</tr>
			<tr>
					<td><code>spec.serviceEntry</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{destinationPorts: &quot;22&quot;, protocol: TCP}</code></td>
					<td></td>
			</tr>
			<tr>
					<td><code>spec.serviceEntry.destinationPorts</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&quot;22&quot;</code></td>
					<td>The destination ports to match. If specified, it must be either a single port (e.g. &ldquo;8080&rdquo;) or a port range (e.g. &ldquo;8090-8095&rdquo;).</td>
			</tr>
			<tr>
					<td><code>spec.serviceEntry.protocol</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>TCP</code></td>
					<td>Protocol supports TCP, UDP and ICMP v4.</td>
			</tr>
			<tr>
					<td><code>spec.serviceEntry.sourcePorts</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>1024-65535</code></td>
					<td>The source ports to match. If specified, it must be either a single port (e.g. &ldquo;8080&rdquo;) or a port range (e.g. &ldquo;8090-8095&rdquo;).</td>
			</tr>
			<tr>
					<td><code>spec.serviceEntry.translatedPorts</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&quot;2222&quot;</code></td>
					<td>The translated ports. If specified, it must be either a single port (e.g. &ldquo;8080&rdquo;) or a port range (e.g. &ldquo;8090-8095&rdquo;).</td>
			</tr>
			<tr>
					<td><code>spec.sourceNetwork</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10.200.0.0/28</code></td>
					<td>SourceNetwork represents the source network address. The value can be a single IPv4 address or CIDR, or a comma separated list of IPv4 addresses.</td>
			</tr>
			<tr>
					<td><code>spec.translatedNetwork</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>10.200.0.10</code></td>
					<td>TranslatedNetwork represents the translated network address. The field is required and must contain a single IPv4 address for SNAT, DNAT and Reflexive.</td>
			</tr>
	</tbody>
</table>

</details></p>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">dnatSsh</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.VPC.Configuration</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">vpc</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.vpc.id}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">vpc.nsx.vmware.com/v1alpha1</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">VPCNATRule</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">configs</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">dnat-ssh</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">action</span><span class="p">:</span><span class="w"> </span><span class="l">DNAT</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">destinationNetwork</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.publicIp.configs[0].spec.allocationIPs}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">translatedNetwork</span><span class="p">:</span><span class="w"> </span><span class="m">10.200.0.10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">serviceEntry</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">destinationPorts</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;22&#34;</span><span class="w">
</span></span></span></code></pre></div><p><strong>Warning: the port did not reach NSX.</strong> VCF Automation&rsquo;s API kept the
<code>serviceEntry</code> (TCP 22), but the rule NSX realized had <code>service: null</code>: a
DNAT of every port on <code>192.168.144.18</code> to the private address. Treat a NAT
rule as a whole-address mapping. To publish one port, use a
<a href="#virtual-machine-service">Virtual Machine Service</a> of type <code>LoadBalancer</code>,
which forwards only its ports and follows the VM&rsquo;s address.</p>
<h3 id="group">Group</h3>
<p><code>kind: VPCNetworkSecurityGroup</code>. A group of addresses, VMs or pods that
firewall rules can name. Default <code>wait</code>: <code>Realized</code>.</p>
<table>
	<thead>
			<tr>
					<th><code>spec</code> field</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>ipAddresses[]</code></td>
					<td>Addresses, ranges or CIDRs.</td>
			</tr>
			<tr>
					<td><code>vmSelectors[]</code></td>
					<td><code>labelSelector</code> (VMs by label, so new VMs with the label join), <code>namespaceSelector</code>, and <code>propertySelector</code> (VMs by <code>Name</code>, <code>OSName</code> or <code>ComputerName</code>, with <code>Equals</code>, <code>Contains</code>, <code>StartsWith</code>, <code>EndsWith</code>, <code>NotEquals</code>).</td>
			</tr>
			<tr>
					<td><code>podSelectors[]</code></td>
					<td><code>labelSelector</code> and <code>namespaceSelector</code> for pods.</td>
			</tr>
			<tr>
					<td><code>vms[]</code></td>
					<td>Specific VMs, by <code>instanceUUID</code>.</td>
			</tr>
			<tr>
					<td><code>vpcNetworkSecurityGroupNames[]</code></td>
					<td>Other groups, nested.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (35)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>spec.ipAddresses</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[10.200.0.0/28]</code></td>
					<td>List of IPs or CIDRs to be included in this VPCNetworkSecurityGroup. Each entry can be a single IP address, an IP range, or a subnet in CIDR notation.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{labelSelector: {matchLabels: {app: web}}, ...}]</code></td>
					<td>List of Pod label selectors that will dynamically select Pods to include in this VPCNetworkSecurityGroup.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors[].labelSelector</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{matchLabels: {app: web}}</code></td>
					<td>A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors[].labelSelector.matchExpressions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: app, operator: In}]</code></td>
					<td>matchExpressions is a list of label selector requirements. The requirements are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors[].labelSelector.matchExpressions[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>app</code></td>
					<td>key is the label key that the selector applies to.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors[].labelSelector.matchExpressions[].operator</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>In</code></td>
					<td>operator represents a key&rsquo;s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors[].labelSelector.matchExpressions[].values</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[web]</code></td>
					<td>values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors[].labelSelector.matchLabels</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{app: web}</code></td>
					<td>matchLabels is a map of {key,value} pairs.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors[].namespaceSelector</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{matchExpressions: [{key: app, operator: In}], matchLabels: {app: web}}</code></td>
					<td>A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors[].namespaceSelector.matchExpressions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: app, operator: In}]</code></td>
					<td>matchExpressions is a list of label selector requirements. The requirements are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors[].namespaceSelector.matchExpressions[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>app</code></td>
					<td>key is the label key that the selector applies to.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors[].namespaceSelector.matchExpressions[].operator</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>In</code></td>
					<td>operator represents a key&rsquo;s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors[].namespaceSelector.matchExpressions[].values</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[web]</code></td>
					<td>values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty.</td>
			</tr>
			<tr>
					<td><code>spec.podSelectors[].namespaceSelector.matchLabels</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{app: web}</code></td>
					<td>matchLabels is a map of {key,value} pairs.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{labelSelector: {matchLabels: {app: web}}, ...}]</code></td>
					<td>List of Virtual Machine label selectors that will dynamically select VMs to include in this VPCNetworkSecurityGroup.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].labelSelector</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{matchLabels: {app: web}}</code></td>
					<td>A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].labelSelector.matchExpressions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: app, operator: In}]</code></td>
					<td>matchExpressions is a list of label selector requirements. The requirements are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].labelSelector.matchExpressions[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>app</code></td>
					<td>key is the label key that the selector applies to.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].labelSelector.matchExpressions[].operator</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>In</code></td>
					<td>operator represents a key&rsquo;s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].labelSelector.matchExpressions[].values</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[web]</code></td>
					<td>values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].labelSelector.matchLabels</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{app: web}</code></td>
					<td>matchLabels is a map of {key,value} pairs.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].namespaceSelector</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{matchExpressions: [{key: app, operator: In}], matchLabels: {app: web}}</code></td>
					<td>A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].namespaceSelector.matchExpressions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: app, operator: In}]</code></td>
					<td>matchExpressions is a list of label selector requirements. The requirements are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].namespaceSelector.matchExpressions[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>app</code></td>
					<td>key is the label key that the selector applies to.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].namespaceSelector.matchExpressions[].operator</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>In</code></td>
					<td>operator represents a key&rsquo;s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].namespaceSelector.matchExpressions[].values</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[web]</code></td>
					<td>values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].namespaceSelector.matchLabels</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{app: web}</code></td>
					<td>matchLabels is a map of {key,value} pairs.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].propertySelector</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{matchExpressions: [{key: Name, operator: StartsWith}]}</code></td>
					<td>PropertySelector represents a set of conditions on VM properties. All MatchExpressions are ANDed; a VM must satisfy all expressions to match.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].propertySelector.matchExpressions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: Name, operator: StartsWith}]</code></td>
					<td>MatchExpressions is a list of property selector requirements. Each requirement consists of a key, operator, and value.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].propertySelector.matchExpressions[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>Name</code></td>
					<td>Key is the VM property to match. Valid keys are Name, OSName and ComputerName.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].propertySelector.matchExpressions[].operator</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>StartsWith</code></td>
					<td>Operator defines how the Key is compared against Value. Valid operators are Equals, Contains, StartsWith, EndsWith and NotEquals.</td>
			</tr>
			<tr>
					<td><code>spec.vmSelectors[].propertySelector.matchExpressions[].value</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>web-</code></td>
					<td>Value is the target value to match against the VM property.</td>
			</tr>
			<tr>
					<td><code>spec.vms</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{instanceUUID: 5010c9b4-1f2e-4d3c-8b7a-6e5f4d3c2b1a}]</code></td>
					<td>List of Virtual Machine references that will be included in this VPCNetworkSecurityGroup.</td>
			</tr>
			<tr>
					<td><code>spec.vms[].instanceUUID</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>5010c9b4-1f2e-4d3c-8b7a-6e5f4d3c2b1a</code></td>
					<td>InstanceUUID of the VM being referenced.</td>
			</tr>
			<tr>
					<td><code>spec.vpcNetworkSecurityGroupNames</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[db-servers]</code></td>
					<td>List of VPCNetworkSecurityGroup names that will be included in this VPCNetworkSecurityGroup.</td>
			</tr>
	</tbody>
</table>

</details></p>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">webGroup</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.VPC.Configuration</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">vpc</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.vpc.id}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">vpc.nsx.vmware.com/v1alpha1</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">VPCNetworkSecurityGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">configs</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">web</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">vmSelectors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span>- <span class="nt">labelSelector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w"> </span>{<span class="nt">tier</span><span class="p">:</span><span class="w"> </span><span class="l">web}</span><span class="w">
</span></span></span></code></pre></div><p>Every VPC also has a group named <code>default</code>, made by NSX.</p>
<h3 id="gateway-firewall-policy">Gateway Firewall Policy</h3>
<p><code>kind: VPCGatewayFirewallPolicy</code>. Rules on the VPC&rsquo;s gateway, for traffic
entering and leaving the VPC. The distributed firewall inside the VPC is a
separate thing.</p>
<table>
	<thead>
			<tr>
					<th><code>spec</code> field</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>rules[]</code></td>
					<td>Per rule: <code>name</code> (unique in the policy), <code>action</code> (<code>Allow</code> default, <code>Drop</code>, <code>Reject</code>, <code>JumpToApplication</code>), <code>direction</code> (<code>InOut</code> default, <code>In</code>, <code>Out</code>), <code>from[]</code> and <code>to[]</code> (each entry <code>groupName</code> or <code>ipAddress</code>), <code>services[]</code> (<code>networkServiceName</code>, or <code>l4PortSet</code> with <code>l4Protocol</code>, <code>destinationPorts</code>, <code>sourcePorts</code>), <code>ipProtocol</code>, <code>log</code>, <code>disabled</code>, <code>notes</code>, <code>tag</code>, <code>sourcesExcluded</code>, <code>destinationsExcluded</code>, <code>appliedTo</code>.</td>
			</tr>
			<tr>
					<td><code>category</code></td>
					<td><code>LocalGatewayRules</code> (default) or <code>Default</code>.</td>
			</tr>
			<tr>
					<td><code>priority</code></td>
					<td>Order against other policies, default 0.</td>
			</tr>
			<tr>
					<td><code>stateful</code>, <code>tcpStrict</code></td>
					<td>Stateful inspection; a full TCP handshake before data.</td>
			</tr>
			<tr>
					<td><code>description</code>, <code>locked</code></td>
					<td></td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (35)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>spec.category</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>LocalGatewayRules</code></td>
					<td>Pre-defined categories for classifying a VPC Gateway Firewall policy.There are two pre-defined categories. They are &ldquo;LocalGatewayRules&rdquo; and &ldquo;Default&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.description</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>Inbound HTTPS</code></td>
					<td>Description for the firewall policy.</td>
			</tr>
			<tr>
					<td><code>spec.isDefault</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>A flag to indicate whether rule is a default rule</td>
			</tr>
			<tr>
					<td><code>spec.locked</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Locked indicates whether a security policy should be locked</td>
			</tr>
			<tr>
					<td><code>spec.priority</code></td>
					<td>integer</td>
					<td></td>
					<td>default <code>0</code></td>
					<td>This field is used to resolve conflicts between multiple Rules under Security or Gateway Policy for a Domain. If no priority is specified in the payload, a value of 0 is assigned by default.</td>
			</tr>
			<tr>
					<td><code>spec.rules</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{action: Allow, name: https-in}]</code></td>
					<td>Rules that are a part of this FirewallPolicy</td>
			</tr>
			<tr>
					<td><code>spec.rules[].action</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>Allow</code></td>
					<td>Action to be applied to all the services</td>
			</tr>
			<tr>
					<td><code>spec.rules[].appliedTo</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{gatewayAttachmentNames: [&lt;transit gateway attachment&gt;], ...}</code></td>
					<td></td>
			</tr>
			<tr>
					<td><code>spec.rules[].appliedTo.gatewayAttachmentNames</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[&lt;transit gateway attachment&gt;]</code></td>
					<td>This field is only applicable when the rule is defined for Transit Gateway Firewall policy</td>
			</tr>
			<tr>
					<td><code>spec.rules[].appliedTo.gatewayNames</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[&lt;transit gateway&gt;]</code></td>
					<td>This field is only applicable when the rule is defined for Transit Gateway Firewall policy</td>
			</tr>
			<tr>
					<td><code>spec.rules[].appliedTo.groupNames</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[&lt;group&gt;]</code></td>
					<td>This field is only applicable when the rule is defined for Distributed Firewall policy</td>
			</tr>
			<tr>
					<td><code>spec.rules[].destinationsExcluded</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>DestinationsExcluded indicates that the rule applies to all destinations <em>except</em> those specified in the &lsquo;To&rsquo; field.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].direction</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>In</code></td>
					<td>Direction defines direction of traffic.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].disabled</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Disabled indicates if the rule is enabled/disabled.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].from</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{ipAddress: 0.0.0.0/0, groupName: admin-hosts}]</code></td>
					<td>From defines the source of the traffic. If empty, it defaults to &ldquo;Any&rdquo;, matching all sources.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].from[].groupName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>admin-hosts</code></td>
					<td></td>
			</tr>
			<tr>
					<td><code>spec.rules[].from[].ipAddress</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>0.0.0.0/0</code></td>
					<td></td>
			</tr>
			<tr>
					<td><code>spec.rules[].ipProtocol</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>IPV4</code></td>
					<td>IpProtocol indicates type of IP packet that should be matched while enforcing the rule. Only IPV_4 protocol is supported for new rules, IPV4_IPV6 is only allowed for default rules.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].isDefault</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>IsDefault is a flag to indicate whether rule is a default rule.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].log</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>Log indicates if traffic matching this rule should be logged.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>https-in</code></td>
					<td>Name for the rule. Must be unique within the policy.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].notes</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>HTTPS from anywhere</code></td>
					<td>Notes for the rule.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].services</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{l4PortSet: {destinationPorts: [443], l4Protocol: TCP}, networkServiceName: :HTTPS}]</code></td>
					<td>Services specifies the network services (protocols and ports) to which this rule applies. If empty or null ,it defaults to &ldquo;Any&rdquo; , then this rule applies to all services.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].services[].l4PortSet</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{destinationPorts: [443], l4Protocol: TCP}</code></td>
					<td>L4PortSetServiceEntry is a ServiceEntry that represents TCP or UDP protocol.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].services[].l4PortSet.destinationPorts</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[443]</code></td>
					<td>DestinationPorts defines the destination port or port range to match. For example: [&ldquo;443&rdquo;], [&ldquo;8080-8090&rdquo;]. If empty, matches any destination port.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].services[].l4PortSet.l4Protocol</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>TCP</code></td>
					<td>L4Protocol specifies the Layer 4 protocol (TCP or UDP).</td>
			</tr>
			<tr>
					<td><code>spec.rules[].services[].l4PortSet.sourcePorts</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[1000-2000]</code></td>
					<td>SourcePorts defines the source port or port range to match. For example: [&ldquo;80&rdquo;], [&ldquo;1000-2000&rdquo;]. If empty, matches any source port.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].services[].networkServiceName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>:HTTPS</code></td>
					<td></td>
			</tr>
			<tr>
					<td><code>spec.rules[].sourcesExcluded</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>SourcesExcluded indicates that the rule applies to all sources <em>except</em> those specified in the &lsquo;From&rsquo; field. When true, the &lsquo;From&rsquo; field acts as an exclusion list.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].tag</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>web</code></td>
					<td>Tag applied on the rule.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].to</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{groupName: ${resource.webGroup.configs[0].name}, ipAddress: 10.200.0.10}]</code></td>
					<td>To defines the destination of the traffic. If empty, it defaults to &ldquo;Any&rdquo;, matching all destinations.</td>
			</tr>
			<tr>
					<td><code>spec.rules[].to[].groupName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>${resource.webGroup.configs[0].name}</code></td>
					<td></td>
			</tr>
			<tr>
					<td><code>spec.rules[].to[].ipAddress</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10.200.0.10</code></td>
					<td></td>
			</tr>
			<tr>
					<td><code>spec.stateful</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Stateful or Stateless nature of security policy is enforced on all rules in this security policy.</td>
			</tr>
			<tr>
					<td><code>spec.tcpStrict</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Ensures that a 3 way TCP handshake is done before the data packets are sent. tcp_strict=true is supported only for stateful security policies.</td>
			</tr>
	</tbody>
</table>

</details></p>

<p>Recipe, HTTPS from anywhere to the web group:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">gwPolicy</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.VPC.Configuration</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">attach]</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">vpc</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.vpc.id}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">vpc.nsx.vmware.com/v1alpha1</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">VPCGatewayFirewallPolicy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">configs</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">web-in</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">stateful</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">rules</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">https-in</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">Allow</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">direction</span><span class="p">:</span><span class="w"> </span><span class="l">In</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">from</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span>- <span class="nt">ipAddress</span><span class="p">:</span><span class="w"> </span><span class="m">0.0.0.0</span><span class="l">/0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">to</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span>- <span class="nt">groupName</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.webGroup.configs[0].name}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span>- <span class="nt">networkServiceName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;:HTTPS&#34;</span><span class="w">
</span></span></span></code></pre></div><p><strong>Gotchas</strong></p>
<ul>
<li><strong><code>from</code> is required</strong>, whatever the field&rsquo;s description says: without it,
<code>spec.rules[0].from: Required value</code>. Write <code>0.0.0.0/0</code> for any source.</li>
<li><strong>Name a service rather than a port set.</strong> A rule that lists only an
<code>l4PortSet</code> gets <code>networkServiceName: Any</code> added by the API, and NSX
realizes it as services <code>ANY</code> beside the raw TCP 443 entry. With
<code>networkServiceName: &quot;:HTTPS&quot;</code> NSX holds exactly <code>/infra/services/HTTPS</code>.
The API lists 415 services, all with a leading colon (<code>:DNS</code>, <code>:HTTPS</code>,
<code>:SSH</code>); without the colon it refuses: <code>Network service name must start with a colon (:), such as :HTTP</code>.</li>
<li><code>groupName</code> takes the group&rsquo;s full name, <code>&lt;vpc&gt;:&lt;generateName&gt;</code>, which
<code>configs[0].name</code> supplies.</li>
<li>The gateway firewall has to be active in the VPC&rsquo;s security profile for any
of this to be enforced.</li>
</ul>
<h2 id="supervisor-resource">Supervisor Resource</h2>
<p><code>type: CCI.Supervisor.Resource</code>. Any Kubernetes object in the namespace,
described by <code>manifest</code>. Every workload item that follows is this type with
<code>apiVersion</code> and <code>kind</code> filled in, so everything here applies to them.</p>
<table>
	<thead>
			<tr>
					<th>Property</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>context</code></td>
					<td><strong>Required.</strong> <code>${resource.&lt;namespace&gt;.id}</code>.</td>
			</tr>
			<tr>
					<td><code>manifest</code></td>
					<td><strong>Required.</strong> The object: <code>apiVersion</code>, <code>kind</code>, <code>metadata</code>, <code>spec</code>. A change to <code>manifest</code> or <code>context</code> recreates the object.</td>
			</tr>
			<tr>
					<td><code>wait</code></td>
					<td>As above.</td>
			</tr>
			<tr>
					<td><code>existing</code></td>
					<td><code>true</code> adopts an object that already exists.</td>
			</tr>
			<tr>
					<td><code>object</code></td>
					<td>Computed: the live object.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (18)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>wait</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{fields: [{path: status.powerState, value: PoweredOn}], ...}</code></td>
					<td>resource yaml</td>
			</tr>
			<tr>
					<td><code>wait.fields</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{path: status.powerState, value: PoweredOn}]</code></td>
					<td>List of fields for whose value needs to be waited for resource to be finished</td>
			</tr>
			<tr>
					<td><code>wait.fields[].path</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>status.powerState</code></td>
					<td>The path of the field within the Kubernetes resource</td>
			</tr>
			<tr>
					<td><code>wait.fields[].value</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>PoweredOn</code></td>
					<td>The value that needs to be met for the wait to be finished.</td>
			</tr>
			<tr>
					<td><code>wait.fields[].indicatesFailure</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>When the condition is met, indicates failure if set to true</td>
			</tr>
			<tr>
					<td><code>wait.conditions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{type: VirtualMachineGuestNetworkConfigSynced, status: True}]</code></td>
					<td>List of conditions that indicate success/failure of resource</td>
			</tr>
			<tr>
					<td><code>wait.conditions[].type</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>VirtualMachineGuestNetworkConfigSynced</code></td>
					<td>The condition type for which to wait</td>
			</tr>
			<tr>
					<td><code>wait.conditions[].reason</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&lt;condition reason&gt;</code></td>
					<td>The condition reason for which to wait</td>
			</tr>
			<tr>
					<td><code>wait.conditions[].status</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>True</code></td>
					<td>The value of the condition that needs to be met</td>
			</tr>
			<tr>
					<td><code>wait.conditions[].indicatesFailure</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>When the condition is met, indicates failure if set to true</td>
			</tr>
			<tr>
					<td><code>wait.executionLogs</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{failureMessagePattern: (?i)error, progressMessagePattern: (?i)creating}</code></td>
					<td>The message to fetch from the logs while the resource is being created. This is only supported for Kubernetes Jobs.</td>
			</tr>
			<tr>
					<td><code>wait.executionLogs.failureMessagePattern</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>(?i)error</code></td>
					<td>The message pattern to check for to fail the resource creation. If the message is found in the logs, the resource creation will be marked as failed.</td>
			</tr>
			<tr>
					<td><code>wait.executionLogs.progressMessagePattern</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>(?i)creating</code></td>
					<td>The message pattern to check for to indicate that the resource creation is in progress. If the message is found in the logs, it will be shown as part of the deployment.</td>
			</tr>
			<tr>
					<td><code>wait.skipWaitOnDelete</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>If false, do not wait for resources to be gone before completing</td>
			</tr>
			<tr>
					<td><code>count</code></td>
					<td>integer</td>
					<td></td>
					<td>default <code>1</code></td>
					<td>The number of resource instances to be created.</td>
			</tr>
			<tr>
					<td><code>context</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>${resource.namespace.id}</code></td>
					<td>The CCI.Supervisor.Namespace resource id</td>
			</tr>
			<tr>
					<td><code>existing</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Use existing supervisor namespace</td>
			</tr>
			<tr>
					<td><code>manifest</code></td>
					<td>object</td>
					<td>yes</td>
					<td>e.g. <code>{apiVersion: vmoperator.vmware.com/v1alpha5, kind: VirtualMachine, spec: ...}</code></td>
					<td>The yaml representation of the Kubernetes resource</td>
			</tr>
	</tbody>
</table>

</details></p>

<p>Recipe, a kind the palette doesn&rsquo;t have. Our labs carry three VLANs over one
trunk subnet with binding maps:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">bmMgmt</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">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">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.namespace.id}</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">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">crd.nsx.vmware.com/v1alpha1</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">SubnetConnectionBindingMap</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">bm-mgmt}</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 class="nt">subnetName</span><span class="p">:</span><span class="w"> </span><span class="nt">sn-mgmt, targetSubnetName</span><span class="p">:</span><span class="w"> </span><span class="nt">sn-trunk, vlanTrafficTag</span><span class="p">:</span><span class="w"> </span><span class="m">1610</span>}<span class="w">
</span></span></span></code></pre></div><p>On the 9.1 Supervisor the namespace&rsquo;s API also offers, among others,
<code>VirtualMachineReplicaSet</code>, <code>VirtualMachineSnapshot</code>, <code>VirtualMachineImage</code>,
<code>SubnetSet</code>, <code>ConfigMap</code> and, with Avi, the Gateway API kinds.</p>
<p><strong>Gotchas</strong></p>
<ul>
<li><code>manifest</code> can be a string as well as a map. Our generator writes one per
host as a string, because a VM with optional sections is easier to build as
text: <code>manifest: &quot;${...}&quot;</code> works as long as the expression returns valid
YAML.</li>
<li>Broadcom&rsquo;s day-2 page warns that bindings don&rsquo;t work for Supervisor
Resources in day-2 operations; a day-2 action has to take the resource as
an input.</li>
</ul>
<h2 id="virtual-machine">Virtual Machine</h2>
<p><code>CCI.Supervisor.Resource</code> with <code>apiVersion: vmoperator.vmware.com/v1alpha5</code>,
<code>kind: VirtualMachine</code>. A VM Service VM: built from a VM class (CPU, memory,
devices) and an image, and configured on first boot by cloud-init, Sysprep,
LinuxPrep or vApp properties.</p>
<p>The most used fields; the complete list of 266 follows.</p>
<table>
	<thead>
			<tr>
					<th><code>spec</code> field</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>className</code></td>
					<td>The VM class. Changing it later resizes the VM.</td>
			</tr>
			<tr>
					<td><code>imageName</code></td>
					<td>The image: its resource name (<code>vmi-0f0136a489b21d06c</code>) or its display name (<code>ubuntu-24.04-server-cloudimg-amd64</code>), if that is unique among the namespace&rsquo;s and the cluster&rsquo;s images.</td>
			</tr>
			<tr>
					<td><code>storageClass</code></td>
					<td>The storage class for the VM&rsquo;s disks.</td>
			</tr>
			<tr>
					<td><code>powerState</code></td>
					<td><code>PoweredOn</code> (default), <code>PoweredOff</code>, <code>Suspended</code>.</td>
			</tr>
			<tr>
					<td><code>guestID</code></td>
					<td>The guest OS identifier. <strong>Required when the VM has a CD-ROM.</strong> Immutable while powered on.</td>
			</tr>
			<tr>
					<td><code>network</code></td>
					<td><code>hostName</code>, <code>domainName</code>, <code>nameservers</code>, <code>searchDomains</code>, <code>disabled</code>, and <code>interfaces[]</code>: <code>name</code> (required), <code>network</code> (a Subnet or SubnetSet), <code>addresses[]</code>, <code>gateway4</code>, <code>dhcp4</code>, <code>mtu</code>, <code>routes[]</code>, <code>nameservers[]</code>, <code>searchDomains[]</code>, <code>guestDeviceName</code>, <code>macAddr</code>. Without <code>interfaces</code>, the VM joins the namespace&rsquo;s default network.</td>
			</tr>
			<tr>
					<td><code>bootstrap</code></td>
					<td>One of <code>cloudInit</code> (inline <code>cloudConfig</code>, <code>rawCloudConfig</code> from a Secret, <code>sshAuthorizedKeys</code>), <code>sysprep</code> (inline or <code>rawSysprep</code> from a Secret), <code>linuxPrep</code> (<code>timeZone</code>, <code>hardwareClockIsUTC</code>, <code>password</code>, <code>scriptText</code>), <code>vAppConfig</code> (<code>properties</code>, <code>rawProperties</code>).</td>
			</tr>
			<tr>
					<td><code>volumes[]</code></td>
					<td>Extra disks from Persistent Volume Claims: <code>name</code>, <code>persistentVolumeClaim.claimName</code>, and per volume <code>controllerType</code> (<code>SCSI</code> default, <code>NVME</code>, <code>SATA</code>, <code>IDE</code>), <code>controllerBusNumber</code>, <code>unitNumber</code>, <code>diskMode</code>, <code>sharingMode</code>, <code>applicationType</code>, <code>removable</code>.</td>
			</tr>
			<tr>
					<td><code>hardware</code></td>
					<td><code>cdrom[]</code> (an ISO image, <code>connected</code>, <code>allowGuestControl</code>) and the controllers: <code>scsiControllers[]</code>, <code>nvmeControllers[]</code>, <code>sataControllers[]</code>, <code>ideControllers[]</code>.</td>
			</tr>
			<tr>
					<td><code>advanced</code></td>
					<td><code>bootDiskCapacity</code>, <code>defaultVolumeProvisioningMode</code> (<code>Thin</code>, <code>Thick</code>, <code>ThickEagerZero</code>), <code>changeBlockTracking</code>.</td>
			</tr>
			<tr>
					<td><code>promoteDisksMode</code></td>
					<td><code>Online</code> (default), <code>Offline</code>, <code>Disabled</code>. See the gotchas.</td>
			</tr>
			<tr>
					<td><code>bootOptions</code></td>
					<td><code>firmware</code> (<code>bios</code>, <code>efi</code>: lower case, whatever the designer suggests), <code>efiSecureBoot</code>, <code>bootOrder</code>, <code>bootDelay</code>, <code>bootRetry</code>, <code>bootRetryDelay</code>, <code>networkBootProtocol</code>.</td>
			</tr>
			<tr>
					<td><code>readinessProbe</code></td>
					<td><code>tcpSocket.port</code>, <code>guestHeartbeat.thresholdStatus</code>, or <code>guestInfo[]</code>, with <code>periodSeconds</code> and <code>timeoutSeconds</code>.</td>
			</tr>
			<tr>
					<td><code>affinity</code></td>
					<td><code>vmAffinity</code> and <code>vmAntiAffinity</code>, each <code>requiredDuringSchedulingPreferredDuringExecution</code> (must hold) or <code>preferredDuringSchedulingPreferredDuringExecution</code> (best effort): a <code>labelSelector</code> and a <code>topologyKey</code>. Needs <code>groupName</code>.</td>
			</tr>
			<tr>
					<td><code>groupName</code></td>
					<td>The Virtual Machine Group the VM belongs to; the group then places it.</td>
			</tr>
			<tr>
					<td><code>crypto</code></td>
					<td><code>encryptionClassName</code>, <code>useDefaultKeyProvider</code> (default <code>true</code>), <code>vTPMMode</code>.</td>
			</tr>
			<tr>
					<td><code>minHardwareVersion</code></td>
					<td>A floor for the virtual hardware version: NVMe needs 14 or later.</td>
			</tr>
			<tr>
					<td><code>nextRestartTime</code></td>
					<td>Set to <code>now</code> to restart the VM, per <code>restartMode</code>.</td>
			</tr>
			<tr>
					<td><code>powerOffMode</code>, <code>suspendMode</code>, <code>restartMode</code></td>
					<td><code>TrySoft</code> (default), <code>Soft</code>, <code>Hard</code>.</td>
			</tr>
			<tr>
					<td><code>currentSnapshotName</code>, <code>policies[]</code>, <code>biosUUID</code>, <code>instanceUUID</code></td>
					<td>Revert to a snapshot, attach policies, pin identifiers.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (266)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>spec.advanced</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{bootDiskCapacity: 40Gi, defaultVolumeProvisioningMode: Thin}</code></td>
					<td>Advanced describes a set of optional, advanced VM configuration options.</td>
			</tr>
			<tr>
					<td><code>spec.advanced.bootDiskCapacity</code></td>
					<td>int or string</td>
					<td></td>
					<td>e.g. <code>40Gi</code></td>
					<td>BootDiskCapacity is the capacity of the VM&rsquo;s boot disk &ndash; the first disk from the VirtualMachineImage from which the VM was deployed.</td>
			</tr>
			<tr>
					<td><code>spec.advanced.changeBlockTracking</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>ChangeBlockTracking is a flag that enables incremental backup support for this VM, a feature utilized by external backup systems such as VMware Data Recovery.</td>
			</tr>
			<tr>
					<td><code>spec.advanced.defaultVolumeProvisioningMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>Thin</code>, <code>Thick</code>, <code>ThickEagerZero</code></td>
					<td>DefaultVolumeProvisioningMode specifies the default provisioning mode for persistent volumes managed by this VM.</td>
			</tr>
			<tr>
					<td><code>spec.affinity</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{vmAntiAffinity: {preferredDuringSchedulingPreferredDuringExecution: [{topologyKey: , ...}]}}</code></td>
					<td>Affinity describes the VM&rsquo;s scheduling constraints.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{preferredDuringSchedulingPreferredDuringExecution: [{labelSelector: {matchLabels: {, ...}}}]}</code></td>
					<td>VMAffinity describes affinity scheduling rules related to other VMs.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.preferredDuringSchedulingPreferredDuringExecution</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{labelSelector: {matchLabels: {app: web}}, topologyKey: kubernetes.io/hostname}]</code></td>
					<td>PreferredDuringSchedulingPreferredDuringExecution describes affinity requirements that should be met, but the VM can still be scheduled if the requirement cannot be satisfied.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.preferredDuringSchedulingPreferredDuringExecution[].labelSelector</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{matchLabels: {app: web}}</code></td>
					<td>LabelSelector is a label query over a set of VMs. When omitted, this term matches with no VMs.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.preferredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: app, operator: In}]</code></td>
					<td>matchExpressions is a list of label selector requirements. The requirements are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.preferredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>app</code></td>
					<td>key is the label key that the selector applies to.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.preferredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions[].operator</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>In</code></td>
					<td>operator represents a key&rsquo;s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.preferredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions[].values</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[web]</code></td>
					<td>values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.preferredDuringSchedulingPreferredDuringExecution[].labelSelector.matchLabels</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{app: web}</code></td>
					<td>matchLabels is a map of {key,value} pairs.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.preferredDuringSchedulingPreferredDuringExecution[].topologyKey</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>kubernetes.io/hostname</code></td>
					<td>TopologyKey describes where this VM should be co-located (affinity) or not co-located (anti-affinity).</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.requiredDuringSchedulingPreferredDuringExecution</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{labelSelector: {matchLabels: {app: web}}, topologyKey: kubernetes.io/hostname}]</code></td>
					<td>RequiredDuringSchedulingPreferredDuringExecution describes affinity requirements that must be met or the VM will not be scheduled.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.requiredDuringSchedulingPreferredDuringExecution[].labelSelector</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{matchLabels: {app: web}}</code></td>
					<td>LabelSelector is a label query over a set of VMs. When omitted, this term matches with no VMs.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.requiredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: app, operator: In}]</code></td>
					<td>matchExpressions is a list of label selector requirements. The requirements are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.requiredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>app</code></td>
					<td>key is the label key that the selector applies to.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.requiredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions[].operator</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>In</code></td>
					<td>operator represents a key&rsquo;s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.requiredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions[].values</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[web]</code></td>
					<td>values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.requiredDuringSchedulingPreferredDuringExecution[].labelSelector.matchLabels</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{app: web}</code></td>
					<td>matchLabels is a map of {key,value} pairs.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAffinity.requiredDuringSchedulingPreferredDuringExecution[].topologyKey</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>kubernetes.io/hostname</code></td>
					<td>TopologyKey describes where this VM should be co-located (affinity) or not co-located (anti-affinity).</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{preferredDuringSchedulingPreferredDuringExecution: [{topologyKey: kubernetes.io/hos, ...}]}</code></td>
					<td>VMAntiAffinity describes anti-affinity scheduling rules related to other VMs.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.preferredDuringSchedulingPreferredDuringExecution</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{topologyKey: kubernetes.io/hostname, labelSelector: {matchLabels: {app: web}}}]</code></td>
					<td>PreferredDuringSchedulingPreferredDuringExecution describes anti-affinity requirements that should be met, but the VM can still be scheduled if the requirement cannot be satisfied.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.preferredDuringSchedulingPreferredDuringExecution[].labelSelector</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{matchLabels: {app: web}}</code></td>
					<td>LabelSelector is a label query over a set of VMs. When omitted, this term matches with no VMs.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.preferredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: app, operator: In}]</code></td>
					<td>matchExpressions is a list of label selector requirements. The requirements are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.preferredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>app</code></td>
					<td>key is the label key that the selector applies to.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.preferredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions[].operator</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>In</code></td>
					<td>operator represents a key&rsquo;s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.preferredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions[].values</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[web]</code></td>
					<td>values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.preferredDuringSchedulingPreferredDuringExecution[].labelSelector.matchLabels</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{app: web}</code></td>
					<td>matchLabels is a map of {key,value} pairs.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.preferredDuringSchedulingPreferredDuringExecution[].topologyKey</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>kubernetes.io/hostname</code></td>
					<td>TopologyKey describes where this VM should be co-located (affinity) or not co-located (anti-affinity).</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.requiredDuringSchedulingPreferredDuringExecution</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{labelSelector: {matchLabels: {app: web}}, topologyKey: kubernetes.io/hostname}]</code></td>
					<td>RequiredDuringSchedulingPreferredDuringExecution describes anti-affinity requirements that must be met or the VM will not be scheduled.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.requiredDuringSchedulingPreferredDuringExecution[].labelSelector</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{matchLabels: {app: web}}</code></td>
					<td>LabelSelector is a label query over a set of VMs. When omitted, this term matches with no VMs.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.requiredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: app, operator: In}]</code></td>
					<td>matchExpressions is a list of label selector requirements. The requirements are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.requiredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>app</code></td>
					<td>key is the label key that the selector applies to.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.requiredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions[].operator</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>In</code></td>
					<td>operator represents a key&rsquo;s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.requiredDuringSchedulingPreferredDuringExecution[].labelSelector.matchExpressions[].values</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[web]</code></td>
					<td>values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.requiredDuringSchedulingPreferredDuringExecution[].labelSelector.matchLabels</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{app: web}</code></td>
					<td>matchLabels is a map of {key,value} pairs.</td>
			</tr>
			<tr>
					<td><code>spec.affinity.vmAntiAffinity.requiredDuringSchedulingPreferredDuringExecution[].topologyKey</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>kubernetes.io/hostname</code></td>
					<td>TopologyKey describes where this VM should be co-located (affinity) or not co-located (anti-affinity).</td>
			</tr>
			<tr>
					<td><code>spec.biosUUID</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>4210d2a5-6d0e-4f6e-9c3a-0b1f2e3d4c5b</code></td>
					<td>BiosUUID describes the desired BIOS UUID for a VM. If omitted, this field defaults to a random UUID.</td>
			</tr>
			<tr>
					<td><code>spec.bootOptions</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{bootDelay: 5s, bootOrder: [{name: &lt;device name&gt;, type: Disk}]}</code></td>
					<td>BootOptions describes the settings that control the boot behavior of the virtual machine. These settings take effect during the next power-on of the virtual machine.</td>
			</tr>
			<tr>
					<td><code>spec.bootOptions.bootDelay</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>5s</code></td>
					<td>BootDelay is the delay before starting the boot sequence. The boot delay specifies a time interval between virtual machine power on or restart and the beginning of the boot sequence.</td>
			</tr>
			<tr>
					<td><code>spec.bootOptions.bootOrder</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: &lt;device name&gt;, type: Disk}]</code></td>
					<td>BootOrder represents the boot order of the virtual machine. After list is exhausted, default BIOS boot device algorithm is used for booting.</td>
			</tr>
			<tr>
					<td><code>spec.bootOptions.bootOrder[].name</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&lt;device name&gt;</code></td>
					<td>Name represents the name of the bootable device. It is required for Disk and Network device types, while ignored for CDRom device types.</td>
			</tr>
			<tr>
					<td><code>spec.bootOptions.bootOrder[].type</code></td>
					<td>string</td>
					<td>yes</td>
					<td><code>Disk</code>, <code>Network</code>, <code>CDRom</code></td>
					<td>Type represents the type of bootable device. The available device types are: - Disk - Network - CDRom</td>
			</tr>
			<tr>
					<td><code>spec.bootOptions.bootRetry</code></td>
					<td>string</td>
					<td></td>
					<td>default <code>Disabled</code></td>
					<td>BootRetry specifies whether a virtual machine that fails to boot will try again.</td>
			</tr>
			<tr>
					<td><code>spec.bootOptions.bootRetryDelay</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10s</code></td>
					<td>BootRetryDelay specifies a time interval between virtual machine boot failure and the subsequent attempt to boot again.</td>
			</tr>
			<tr>
					<td><code>spec.bootOptions.efiSecureBoot</code></td>
					<td>string</td>
					<td></td>
					<td><code>Enabled</code>, <code>Disabled</code>; default <code>Disabled</code></td>
					<td>EFISecureBoot specifies whether the virtual machine&rsquo;s firmware will perform signature checks of any EFI images loaded during startup.</td>
			</tr>
			<tr>
					<td><code>spec.bootOptions.firmware</code></td>
					<td>string</td>
					<td></td>
					<td><code>bios</code>, <code>efi</code></td>
					<td>Firmware represents the firmware for the virtual machine to use. Any update to this value after the virtual machine has already been created will be ignored.</td>
			</tr>
			<tr>
					<td><code>spec.bootOptions.networkBootProtocol</code></td>
					<td>string</td>
					<td></td>
					<td><code>IP4</code>, <code>IP6</code>; default <code>IP4</code></td>
					<td>NetworkBootProtocol is the protocol to attempt during PXE network boot or NetBoot. The available protocols are: - IP4 &ndash; PXE (or Apple NetBoot) over IPv4.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{cloudInit: {cloudConfig: {timezone: Europe/London, users: [{name: ops, ...}]}}}</code></td>
					<td>Bootstrap describes the desired state of the guest&rsquo;s bootstrap configuration. If omitted, a default bootstrap method may be selected based on the guest OS identifier.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{cloudConfig: {timezone: Europe/London, users: [{name: ops, ...}]}}</code></td>
					<td>CloudInit may be used to bootstrap Linux guests with Cloud-Init or Windows guests that support Cloudbase-Init.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{timezone: Europe/London, users: [{name: ops, hashed_passwd: {key: ops-passwd, ...}}]}</code></td>
					<td>CloudConfig describes a subset of a Cloud-Init CloudConfig, used to bootstrap the VM.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.defaultUserEnabled</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>DefaultUserEnabled may be set to true to ensure even if the Users field is not empty, the default user is still created on systems that have one defined.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.runcmd</code></td>
					<td>any</td>
					<td></td>
					<td>e.g. <code>[systemctl enable --now nginx]</code></td>
					<td>RunCmd allows running one or more commands on the guest. The entries in this list can adhere to two, different formats: Format 1 &ndash; a string that contains the command and its arguments, ex.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.ssh_pwauth</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>SSHPwdAuth sets whether or not to accept password authentication. In order for this config to be applied, SSH may need to be restarted.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.timezone</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>Europe/London</code></td>
					<td>Timezone describes the timezone represented in /usr/share/zoneinfo.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: ops, hashed_passwd: {key: ops-passwd, name: web-pw}}]</code></td>
					<td>Users allows adding/configuring one or more users on the guest.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].create_groups</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>CreateGroups is a flag that may be set to false to disable creation of specified user groups. Defaults to true when Name is not &ldquo;default&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].expiredate</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>2027-01-01</code></td>
					<td>ExpireData is the date on which the user&rsquo;s account will be disabled.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].gecos</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>Operations user</code></td>
					<td>Gecos is an optional comment about the user, usually a comma-separated string of the user&rsquo;s real name and contact information.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].groups</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[sudo]</code></td>
					<td>Groups is an optional list of groups to add to the user.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].hashed_passwd</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{key: ops-passwd, name: web-pw}</code></td>
					<td>HashedPasswd is a hash of the user&rsquo;s password that will be applied even if the specified user already exists.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].hashed_passwd.key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>ops-passwd</code></td>
					<td>Key is the key in the secret that specifies the requested data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].hashed_passwd.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>web-pw</code></td>
					<td>Name is the name of the secret.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].homedir</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>/home/ops</code></td>
					<td>Homedir is the optional home directory for the user. Defaults to &ldquo;/home/<!-- raw HTML omitted -->&rdquo; when Name is not &ldquo;default&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].inactive</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>30</code></td>
					<td>Inactive optionally represents the number of days until the user is disabled.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].lock_passwd</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>false</code></td>
					<td>LockPasswd disables password login. Defaults to true when Name is not &ldquo;default&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>ops</code></td>
					<td>Name is the user&rsquo;s login name. When set to &ldquo;default&rdquo;, all other fields from this User must be nil.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].no_create_home</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>NoCreateHome prevents the creation of the home directory. Defaults to false when Name is not &ldquo;default&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].no_log_init</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>NoLogInit prevents the initialization of lastlog and faillog for the user. Defaults to false when Name is not &ldquo;default&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].no_user_group</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>NoUserGroup prevents the creation of the group named after the user. Defaults to false when Name is not &ldquo;default&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].passwd</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{key: &lt;key in the Secret&gt;, name: &lt;Secret name&gt;}</code></td>
					<td>Passwd is a hash of the user&rsquo;s password that will be applied only to a newly created user. To apply a new, hashed password to an existing user please use HashedPasswd instead.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].passwd.key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;key in the Secret&gt;</code></td>
					<td>Key is the key in the secret that specifies the requested data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].passwd.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;Secret name&gt;</code></td>
					<td>Name is the name of the secret.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].primary_group</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>ops</code></td>
					<td>PrimaryGroup is the primary group for the user. Defaults to the value of the Name field when it is not &ldquo;default&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].selinux_user</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>staff_u</code></td>
					<td>SELinuxUser is the SELinux user for the user&rsquo;s login.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].shell</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>/bin/bash</code></td>
					<td>Shell is the path to the user&rsquo;s login shell.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].snapuser</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>ops@example.com</code></td>
					<td>SnapUser specifies an e-mail address to create the user as a Snappy user through &ldquo;snap create-user&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].ssh_authorized_keys</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOkJfr8Q3cNq ops@example]</code></td>
					<td>SSHAuthorizedKeys is a list of SSH keys to add to the user&rsquo;s authorized keys file.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].ssh_import_id</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[gh:octocat]</code></td>
					<td>SSHImportID is a list of SSH IDs to import for the user.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].ssh_redirect_user</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>SSHRedirectUser may be set to true to disable SSH logins for this user. Any SSH login as this user will timeout with a message to login instead as the default user.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].sudo</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>ALL=(ALL) NOPASSWD:ALL</code></td>
					<td>Sudo is a sudo rule to apply to the user. When omitted, no sudo rules will be applied to the user.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].system</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>System is an optional flag that indicates the user should be created as a system user with no home directory. Defaults to false when Name is not &ldquo;default&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.users[].uid</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>1001</code></td>
					<td>UID is the user&rsquo;s ID. When omitted the guest will default to the next available number.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.write_files</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{path: /etc/nginx/conf.d/lab.conf, content: server_tokens off;}]</code></td>
					<td>WriteFiles allows adding files to the guest file system.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.write_files[].append</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>Append specifies whether or not to append the content to an existing file if the file specified by Path already exists.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.write_files[].content</code></td>
					<td>any</td>
					<td></td>
					<td>e.g. <code>server_tokens off;</code></td>
					<td>Content is the optional content to write to the provided Path. When omitted an empty file will be created or existing file will be modified.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.write_files[].defer</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>Defer indicates to defer writing the file until Cloud-Init&rsquo;s &ldquo;final&rdquo; stage, after users are created and packages are installed.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.write_files[].encoding</code></td>
					<td>string</td>
					<td></td>
					<td><code>b64</code>, <code>base64</code>, <code>gz</code>, <code>gzip</code>, <code>gz+b64</code>, <code>gz+base64</code>, <code>gzip+b64</code>, <code>gzip+base64</code>, <code>text/plain</code>; default <code>text/plain</code></td>
					<td>Encoding is an optional encoding type of the content.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.write_files[].owner</code></td>
					<td>string</td>
					<td></td>
					<td>default <code>root:root</code></td>
					<td>Owner is an optional &ldquo;owner:group&rdquo; to chown the file.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.write_files[].path</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>/etc/nginx/conf.d/lab.conf</code></td>
					<td>Path is the path of the file to which the content is decoded and written.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.cloudConfig.write_files[].permissions</code></td>
					<td>string</td>
					<td></td>
					<td>default <code>0644</code></td>
					<td>Permissions an optional set of file permissions to set. &ldquo;0###&rdquo;. When omitted the guest will default this value to &ldquo;0644&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.instanceID</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>web-01-v2</code></td>
					<td>InstanceID is the cloud-init metadata instance ID. If omitted, this field defaults to the VM&rsquo;s BiosUUID.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.rawCloudConfig</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{key: user-data, name: jump-bootstrap}</code></td>
					<td>RawCloudConfig describes a key in a Secret resource that contains the CloudConfig data used to bootstrap the VM.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.rawCloudConfig.key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>user-data</code></td>
					<td>Key is the key in the secret that specifies the requested data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.rawCloudConfig.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>jump-bootstrap</code></td>
					<td>Name is the name of the secret.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.sshAuthorizedKeys</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[ssh-ed25519 AAAA... ops@admin]</code></td>
					<td>SSHAuthorizedKeys is a list of public keys that CloudInit will apply to the guest&rsquo;s default user.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.useGlobalNameserversAsDefault</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>UseGlobalNameserversAsDefault will use the global nameservers specified in the NetworkSpec as the per-interface nameservers when the per-interface nameservers is not provided.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.useGlobalSearchDomainsAsDefault</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>UseGlobalSearchDomainsAsDefault will use the global search domains specified in the NetworkSpec as the per-interface search domains when the per-interface search domains is not provided.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.waitOnNetwork4</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>WaitOnNetwork4 indicates whether the cloud-init datasource should wait for an IPv4 address to be available before writing the instance-data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.cloudInit.waitOnNetwork6</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>WaitOnNetwork6 indicates whether the cloud-init datasource should wait for an IPv6 address to be available before writing the instance-data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{password: {name: &lt;Secret name&gt;, key: password}, ...}</code></td>
					<td>LinuxPrep may be used to bootstrap Linux guests. The guest&rsquo;s networking stack is configured by Guest OS Customization (GOSC).</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep.customizeAtNextPowerOn</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>CustomizeAtNextPowerOn describes when customization is performed on the VM. When set to false, the VM will not be customized at the next power on.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep.expirePasswordAfterNextLogin</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>ExpirePasswordAfterNextLogin indicates whether or not the root account is required to change their password after the next login.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep.hardwareClockIsUTC</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>HardwareClockIsUTC specifies whether the hardware clock is in UTC or local time.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep.password</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{name: &lt;Secret name&gt;, key: password}</code></td>
					<td>Password is the new root password for the machine. When not explicitly specified, the Key field for the selector defaults to <code>password</code>.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep.password.key</code></td>
					<td>string</td>
					<td></td>
					<td>default <code>password</code></td>
					<td>Key is the key in the secret that specifies the requested data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep.password.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;Secret name&gt;</code></td>
					<td>Name is the name of the secret.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep.scriptText</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{from: {key: &lt;key in the Secret&gt;, name: &lt;Secret name&gt;}, value: '#!/bin/sh ...'}</code></td>
					<td>ScriptText is the script to run before and after customization. Please see <a href="https://knowledge.broadcom.com/external/article?legacyId=1026614">https://knowledge.broadcom.com/external/article?legacyId=1026614</a> for script examples.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep.scriptText.from</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{key: &lt;key in the Secret&gt;, name: &lt;Secret name&gt;}</code></td>
					<td>From is specified to reference a value from a Secret resource.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep.scriptText.from.key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;key in the Secret&gt;</code></td>
					<td>Key is the key in the secret that specifies the requested data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep.scriptText.from.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;Secret name&gt;</code></td>
					<td>Name is the name of the secret.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep.scriptText.value</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>'#!/bin/sh ...'</code></td>
					<td>Value is used to directly specify a value.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.linuxPrep.timeZone</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>Europe/London</code></td>
					<td>TimeZone is a case-sensitive timezone, such as Europe/Sofia. Valid values are based on the tz (timezone) database used by Linux and other Unix systems.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{rawSysprep: {key: &lt;key in the Secret&gt;, name: &lt;Secret name&gt;}, ...}</code></td>
					<td>Sysprep may be used to bootstrap Windows guests. The guest&rsquo;s networking stack is configured by Guest OS Customization (GOSC).</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.customizeAtNextPowerOn</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>CustomizeAtNextPowerOn describes when customization is performed on the VM. When set to false, the VM will not be customized at the next power on.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.rawSysprep</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{key: &lt;key in the Secret&gt;, name: &lt;Secret name&gt;}</code></td>
					<td>RawSysprep describes a key in a Secret resource that contains an XML string of the Sysprep text used to bootstrap the VM.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.rawSysprep.key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;key in the Secret&gt;</code></td>
					<td>Key is the key in the secret that specifies the requested data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.rawSysprep.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;Secret name&gt;</code></td>
					<td>Name is the name of the secret.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{guiRunOnce: {commands: [powershell -File C:\setup.ps1]}, ...}</code></td>
					<td>Sysprep is an object representation of a Windows sysprep.xml answer file. This field encloses all the individual keys listed in a sysprep.xml file.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.expirePasswordAfterNextLogin</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>ExpirePasswordAfterNextLogin indicates whether or not the local Administrators group accounts are required to change their password after the next login.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.guiRunOnce</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{commands: [powershell -File C:\setup.ps1]}</code></td>
					<td>GUIRunOnce is a representation of the Sysprep GuiRunOnce key.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.guiRunOnce.commands</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[powershell -File C:\setup.ps1]</code></td>
					<td>Commands is a list of commands to run at first user logon, after guest customization.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.guiUnattended</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{autoLogonCount: 1, password: {name: &lt;Secret name&gt;, key: password}}</code></td>
					<td>GUIUnattended is a representation of the Sysprep GUIUnattended key.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.guiUnattended.autoLogon</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>AutoLogon determine whether the machine automatically logs on as Administrator.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.guiUnattended.autoLogonCount</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>1</code></td>
					<td>AutoLogonCount specifies the number of times the machine should automatically log on as Administrator.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.guiUnattended.password</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{name: &lt;Secret name&gt;, key: password}</code></td>
					<td>Password is the new administrator password for the machine. To specify that the password should be set to blank (that is, no password), set the password value to NULL.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.guiUnattended.password.key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>default <code>password</code></td>
					<td>Key is the key in the secret that specifies the requested data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.guiUnattended.password.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;Secret name&gt;</code></td>
					<td>Name is the name of the secret.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.guiUnattended.timeZone</code></td>
					<td>integer</td>
					<td></td>
					<td>default <code>85</code></td>
					<td>TimeZone is the time zone index for the virtual machine.ly/3Rzv8oL. Defaults to UTC.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.identification</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{domainAdmin: svc-join@example.local, domainAdminPassword: {name: &lt;Secret name&gt;, ...}}</code></td>
					<td>Identification is a representation of the Sysprep Identification key.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.identification.domainAdmin</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>svc-join@example.local</code></td>
					<td>DomainAdmin is the domain user account used for authentication if the virtual machine is joining a domain.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.identification.domainAdminPassword</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{name: &lt;Secret name&gt;, key: domain_admin_password}</code></td>
					<td>DomainAdminPassword is the password for the domain user account used for authentication if the virtual machine is joining a domain.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.identification.domainAdminPassword.key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>default <code>domain_admin_password</code></td>
					<td>Key is the key in the secret that specifies the requested data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.identification.domainAdminPassword.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;Secret name&gt;</code></td>
					<td>Name is the name of the secret.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.identification.domainOU</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>OU=Servers,DC=example,DC=local</code></td>
					<td>DomainOU is the MachineObjectOU which specifies the full LDAP path name of the OU to which the computer belongs.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.identification.joinWorkgroup</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>WORKGROUP</code></td>
					<td>JoinWorkgroup is the workgroup that the virtual machine should join.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.licenseFilePrintData</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{autoUsers: 5, autoMode: perSeat}</code></td>
					<td>LicenseFilePrintData is a representation of the Sysprep LicenseFilePrintData key.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.licenseFilePrintData.autoMode</code></td>
					<td>string</td>
					<td>yes</td>
					<td><code>perSeat</code>, <code>perServer</code></td>
					<td>AutoMode specifies the server licensing mode.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.licenseFilePrintData.autoUsers</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>5</code></td>
					<td>AutoUsers indicates the number of client licenses purchased for the VirtualCenter server being installed.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.scriptText</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{from: {key: &lt;key in the Secret&gt;, name: &lt;Secret name&gt;}, ...}</code></td>
					<td>ScriptText describes the script to run before and after customization. The script must be a Windows batch file.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.scriptText.from</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{key: &lt;key in the Secret&gt;, name: &lt;Secret name&gt;}</code></td>
					<td>From is specified to reference a value from a Secret resource.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.scriptText.from.key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;key in the Secret&gt;</code></td>
					<td>Key is the key in the secret that specifies the requested data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.scriptText.from.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;Secret name&gt;</code></td>
					<td>Name is the name of the secret.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.scriptText.value</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>powershell -File C:\setup.ps1</code></td>
					<td>Value is used to directly specify a value.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.userData</code></td>
					<td>object</td>
					<td>yes</td>
					<td>e.g. <code>{fullName: Lab Admin, orgName: Example Ltd}</code></td>
					<td>UserData is a representation of the Sysprep UserData key.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.userData.fullName</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>Lab Admin</code></td>
					<td>FullName is the user&rsquo;s full name.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.userData.orgName</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>Example Ltd</code></td>
					<td>OrgName is the name of the user&rsquo;s organization.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.userData.productID</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{name: &lt;Secret name&gt;, key: product_id}</code></td>
					<td>ProductID is a valid serial number. When not explicitly specified, the Key field for the selector defaults to <code>domain_admin_password</code>.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.userData.productID.key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>default <code>product_id</code></td>
					<td>Key is the key in the secret that specifies the requested data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.sysprep.sysprep.userData.productID.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;Secret name&gt;</code></td>
					<td>Name is the name of the secret.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.vAppConfig</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{properties: [{key: guestinfo.hostname, value: {from: {key: &lt;key in the Secret&gt;, ...}}}]}</code></td>
					<td>VAppConfig may be used to bootstrap guests that rely on vApp properties (how VMware surfaces OVF properties on guests) to transport data into the guest.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.vAppConfig.properties</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: guestinfo.hostname, value: {from: {key: &lt;key in the Secret&gt;, ...}}}]</code></td>
					<td>Properties is a list of vApp/OVF property key/value pairs.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.vAppConfig.properties[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>guestinfo.hostname</code></td>
					<td>Key is the key part of the key/value pair.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.vAppConfig.properties[].value</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{from: {key: &lt;key in the Secret&gt;, name: &lt;Secret name&gt;}, value: web-01}</code></td>
					<td>Value is the optional value part of the key/value pair.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.vAppConfig.properties[].value.from</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{key: &lt;key in the Secret&gt;, name: &lt;Secret name&gt;}</code></td>
					<td>From is specified to reference a value from a Secret resource.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.vAppConfig.properties[].value.from.key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;key in the Secret&gt;</code></td>
					<td>Key is the key in the secret that specifies the requested data.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.vAppConfig.properties[].value.from.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;Secret name&gt;</code></td>
					<td>Name is the name of the secret.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.vAppConfig.properties[].value.value</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>web-01</code></td>
					<td>Value is used to directly specify a value.</td>
			</tr>
			<tr>
					<td><code>spec.bootstrap.vAppConfig.rawProperties</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&lt;ConfigMap with the OVF properties&gt;</code></td>
					<td>RawProperties is the name of a Secret resource in the same Namespace as this VM where each key/value pair from the Secret is used as a vApp key/value pair.</td>
			</tr>
			<tr>
					<td><code>spec.class</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{kind: VirtualMachineClass, name: best-effort-small}</code></td>
					<td>Class describes the VirtualMachineClassInstance resource that is referenced by this virtual machine.</td>
			</tr>
			<tr>
					<td><code>spec.class.apiVersion</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>vmoperator.vmware.com/v1alpha5</code></td>
					<td>APIVersion defines the versioned schema of this representation of an object.</td>
			</tr>
			<tr>
					<td><code>spec.class.kind</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>VirtualMachineClass</code></td>
					<td>Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to.</td>
			</tr>
			<tr>
					<td><code>spec.class.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>best-effort-small</code></td>
					<td>Name refers to a unique resource in the current namespace.</td>
			</tr>
			<tr>
					<td><code>spec.className</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>best-effort-small</code></td>
					<td>ClassName describes the name of the VirtualMachineClass resource used to deploy this VM.</td>
			</tr>
			<tr>
					<td><code>spec.crypto</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{encryptionClassName: &lt;encryption class&gt;, useDefaultKeyProvider: true}</code></td>
					<td>Crypto describes the desired encryption state of the VirtualMachine.</td>
			</tr>
			<tr>
					<td><code>spec.crypto.encryptionClassName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&lt;encryption class&gt;</code></td>
					<td>EncryptionClassName describes the name of the EncryptionClass resource used to encrypt this VM.</td>
			</tr>
			<tr>
					<td><code>spec.crypto.useDefaultKeyProvider</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>true</code></td>
					<td>UseDefaultKeyProvider describes the desired behavior for when an explicit EncryptionClass is not provided.</td>
			</tr>
			<tr>
					<td><code>spec.crypto.vTPMMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>Clone</code>, <code>New</code>; default <code>New</code></td>
					<td>VTPMMode describes the desired behavior when deploying a VirtualMachine using a VirtualMachine-backed image which created from an encrypted VirtualMachine with a vTPM.</td>
			</tr>
			<tr>
					<td><code>spec.currentSnapshotName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>before-upgrade</code></td>
					<td>CurrentSnapshotName represents the desired snapshot that the VM should point to. This field can be specified to revert the VM to a given snapshot.</td>
			</tr>
			<tr>
					<td><code>spec.groupName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>web</code></td>
					<td>GroupName indicates the name of the VirtualMachineGroup to which this VM belongs. VMs that belong to a group do not drive their own placement, rather that is handled by the group.</td>
			</tr>
			<tr>
					<td><code>spec.guestID</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>vmkernel9Guest</code></td>
					<td>GuestID describes the desired guest operating system identifier for a VM. The logic that determines the guest ID is as follows: If this field is set, then its value is used.</td>
			</tr>
			<tr>
					<td><code>spec.hardware</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{cdrom: [{name: cdrom0, image: {kind: VirtualMachineImage, ...}}]}</code></td>
					<td>Hardware describes the VM&rsquo;s desired hardware.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.cdrom</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: cdrom0, image: {kind: VirtualMachineImage, name: vmi-e400a813bbd5d52a5}}]</code></td>
					<td>Cdrom describes the desired state of the VM&rsquo;s CD-ROM devices. Each CD-ROM device requires a reference to an ISO-type VirtualMachineImage or ClusterVirtualMachineImage resource as backing.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.cdrom[].allowGuestControl</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>true</code></td>
					<td>AllowGuestControl describes whether or not a web console connection may be used to connect/disconnect the CD-ROM device.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.cdrom[].connected</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>true</code></td>
					<td>Connected describes the desired connection state of the CD-ROM device. When true, the CD-ROM device is added and connected to the VM.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.cdrom[].controllerBusNumber</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>0</code></td>
					<td>ControllerBusNumber describes the bus number of the controller to which this CD-ROM should be attached.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.cdrom[].controllerType</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>SATA</code></td>
					<td>ControllerType describes the type of the controller to which this CD-ROM should be attached.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.cdrom[].image</code></td>
					<td>object</td>
					<td>yes</td>
					<td>e.g. <code>{kind: VirtualMachineImage, name: vmi-e400a813bbd5d52a5}</code></td>
					<td>Image describes the reference to an ISO type VirtualMachineImage or ClusterVirtualMachineImage resource used as the backing for the CD-ROM.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.cdrom[].image.kind</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>VirtualMachineImage</code></td>
					<td>Kind describes the type of image, either a namespace-scoped VirtualMachineImage or cluster-scoped ClusterVirtualMachineImage.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.cdrom[].image.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>vmi-e400a813bbd5d52a5</code></td>
					<td>Name refers to the name of a VirtualMachineImage resource in the same namespace as this VM or a cluster-scoped ClusterVirtualMachineImage.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.cdrom[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>cdrom0</code></td>
					<td>Name consists of at least two lowercase letters or digits of this CD-ROM. It must be unique among all CD-ROM devices attached to the VM.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.cdrom[].unitNumber</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>1</code></td>
					<td>UnitNumber describes the desired unit number for attaching the CD-ROM to a storage controller. When omitted, the next available unit number of the selected controller is used.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.ideControllers</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{busNumber: 0}]</code></td>
					<td>IDEControllers describes the desired list of IDE controllers for the VM. Defaults to two IDE controllers, with bus 0 and bus 1.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.ideControllers[].busNumber</code></td>
					<td>integer</td>
					<td>yes</td>
					<td>e.g. <code>0</code></td>
					<td>BusNumber describes the desired bus number of the controller.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.nvmeControllers</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{busNumber: 0, sharingMode: None}]</code></td>
					<td>NVMEControllers describes the desired list of NVME controllers for the VM.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.nvmeControllers[].busNumber</code></td>
					<td>integer</td>
					<td>yes</td>
					<td>e.g. <code>0</code></td>
					<td>BusNumber describes the desired bus number of the controller.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.nvmeControllers[].sharingMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>None</code>, <code>Physical</code>; default <code>None</code></td>
					<td>SharingMode describes the sharing mode for the controller. Defaults to None.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.sataControllers</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{busNumber: 0}]</code></td>
					<td>SATAControllers describes the desired list of SATA controllers for the VM.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.sataControllers[].busNumber</code></td>
					<td>integer</td>
					<td>yes</td>
					<td>e.g. <code>0</code></td>
					<td>BusNumber describes the desired bus number of the controller.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.scsiControllers</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{busNumber: 0, type: ParaVirtual}]</code></td>
					<td>SCSIControllers describes the desired list of SCSI controllers for the VM.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.scsiControllers[].busNumber</code></td>
					<td>integer</td>
					<td>yes</td>
					<td>e.g. <code>0</code></td>
					<td>BusNumber describes the desired bus number of the controller.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.scsiControllers[].sharingMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>None</code>, <code>Physical</code>, <code>Virtual</code>; default <code>None</code></td>
					<td>SharingMode describes the sharing mode for the controller. Defaults to None.</td>
			</tr>
			<tr>
					<td><code>spec.hardware.scsiControllers[].type</code></td>
					<td>string</td>
					<td></td>
					<td><code>ParaVirtual</code>, <code>BusLogic</code>, <code>LsiLogic</code>, <code>LsiLogicSAS</code>; default <code>ParaVirtual</code></td>
					<td>Type describes the desired type of SCSI controller. Defaults to ParaVirtual.</td>
			</tr>
			<tr>
					<td><code>spec.image</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{kind: VirtualMachineImage, name: vmi-0123456789abcdef0}</code></td>
					<td>Image describes the reference to the VirtualMachineImage or ClusterVirtualMachineImage resource used to deploy this VM.imageName, the value of spec.image.name MUST be a Kubernetes object &hellip;</td>
			</tr>
			<tr>
					<td><code>spec.image.kind</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>VirtualMachineImage</code></td>
					<td>Kind describes the type of image, either a namespace-scoped VirtualMachineImage or cluster-scoped ClusterVirtualMachineImage.</td>
			</tr>
			<tr>
					<td><code>spec.image.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>vmi-0123456789abcdef0</code></td>
					<td>Name refers to the name of a VirtualMachineImage resource in the same namespace as this VM or a cluster-scoped ClusterVirtualMachineImage.</td>
			</tr>
			<tr>
					<td><code>spec.imageName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>ubuntu-24.04-server-cloudimg-amd64</code></td>
					<td>ImageName describes the name of the image resource used to deploy this VM. This field may be used to specify the name of a VirtualMachineImage or ClusterVirtualMachineImage resource.</td>
			</tr>
			<tr>
					<td><code>spec.instanceUUID</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>5010c9b4-1f2e-4d3c-8b7a-6e5f4d3c2b1a</code></td>
					<td>InstanceUUID describes the desired Instance UUID for a VM. If omitted, this field defaults to a random UUID. This value is only used for the VM Instance UUID, it is not used within cloudInit.</td>
			</tr>
			<tr>
					<td><code>spec.minHardwareVersion</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>21</code></td>
					<td>MinHardwareVersion describes the desired, minimum hardware version. The logic that determines the hardware version is as follows: 1.</td>
			</tr>
			<tr>
					<td><code>spec.network</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{hostName: dc01, interfaces: [{name: eth0, addresses: [172.30.0.34/27]}]}</code></td>
					<td>Network describes the desired network configuration for the VM.</td>
			</tr>
			<tr>
					<td><code>spec.network.disabled</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>Disabled is a flag that indicates whether or not to disable networking for this VM.</td>
			</tr>
			<tr>
					<td><code>spec.network.domainName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>lab.local</code></td>
					<td>DomainName describes the value the guest uses as its domain name.</td>
			</tr>
			<tr>
					<td><code>spec.network.hostName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>dc01</code></td>
					<td>HostName describes the value the guest uses as its host name. If omitted, the name of the VM will be used.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: eth0, addresses: [172.30.0.34/27]}]</code></td>
					<td>Interfaces is the list of network interfaces used by this VM. If the Interfaces field is empty and the Disabled field is false, then a default interface with the name eth0 will be created.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].addresses</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[172.30.0.34/27]</code></td>
					<td>Addresses is an optional list of IP4 or IP6 addresses to assign to this interface. 192.168.0.10/24 or 2001:db8:101::a/64.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].dhcp4</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>DHCP4 indicates whether or not this interface uses DHCP for IP4 networking.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].dhcp6</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>DHCP6 indicates whether or not this interface uses DHCP for IP6 networking.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].gateway4</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>172.30.0.33</code></td>
					<td>Gateway4 is the default, IP4 gateway for this interface. If unset, the gateway from the network provider will be used.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].gateway6</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>fd00::1</code></td>
					<td>Gateway6 is the primary IP6 gateway for this interface. If unset, the gateway from the network provider will be used.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].guestDeviceName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>eth0</code></td>
					<td>GuestDeviceName is used to rename the device inside the guest when the bootstrap provider is Cloud-Init. dvd, cdrom, sda, etc.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].macAddr</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>00:50:56:00:00:10</code></td>
					<td>MACAddr is the optional MAC address of this interface. If no MAC address is provided, one will be generated by either the network provider or vCenter.nsx.vmware.com.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].mtu</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>1500</code></td>
					<td>MTU is the Maximum Transmission Unit size in bytes.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>eth0</code></td>
					<td>Name describes the unique name of this network interface, used to distinguish it from other network interfaces attached to this VM.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].nameservers</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[172.30.0.34]</code></td>
					<td>Nameservers is a list of IP4 and/or IP6 addresses used as DNS nameservers.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].network</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{kind: Subnet, name: sn-mgmt}</code></td>
					<td>Network is the name of the network resource to which this interface is connected. If no network is provided, then this interface will be connected to the Namespace&rsquo;s default network.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].network.apiVersion</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>crd.nsx.vmware.com/v1alpha1</code></td>
					<td>APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].network.kind</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>Subnet</code></td>
					<td>Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].network.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>sn-mgmt</code></td>
					<td>Name refers to a unique resource in the current namespace.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].routes</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{to: 172.16.0.0/16, via: 10.200.0.1}]</code></td>
					<td>Routes is a list of optional, static routes.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].routes[].metric</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>100</code></td>
					<td>Metric is the weight/priority of the route.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].routes[].to</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>172.16.0.0/16</code></td>
					<td>To is either &ldquo;default&rdquo;, or an IP4 or IP6 address.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].routes[].via</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>10.200.0.1</code></td>
					<td>Via is an IP4 or IP6 address.</td>
			</tr>
			<tr>
					<td><code>spec.network.interfaces[].searchDomains</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[lab.local]</code></td>
					<td>SearchDomains is a list of search domains used when resolving IP addresses with DNS.</td>
			</tr>
			<tr>
					<td><code>spec.network.nameservers</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[10.200.0.2]</code></td>
					<td>Nameservers is a list of IP4 and/or IP6 addresses used as DNS nameservers. These are applied globally. The Cloud-Init bootstrap provider supports per-interface nameservers.</td>
			</tr>
			<tr>
					<td><code>spec.network.searchDomains</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[lab.local]</code></td>
					<td>SearchDomains is a list of search domains used when resolving IP addresses with DNS. These are applied globally. The Cloud-Init bootstrap provider supports per-interface search domains.</td>
			</tr>
			<tr>
					<td><code>spec.nextRestartTime</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>now</code></td>
					<td>NextRestartTime may be used to restart the VM, in accordance with RestartMode, by setting the value of this field to &ldquo;now&rdquo; (case-insensitive).</td>
			</tr>
			<tr>
					<td><code>spec.policies</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{kind: ComputePolicy, name: &lt;compute policy&gt;}]</code></td>
					<td>Policies describes a list of policies that should be explicitly applied to this VM. Please consult a policy to determine if it may be applied directly.</td>
			</tr>
			<tr>
					<td><code>spec.policies[].apiVersion</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>vsphere.policy.vmware.com/v1alpha1</code></td>
					<td>APIVersion defines the versioned schema of this representation of an object.</td>
			</tr>
			<tr>
					<td><code>spec.policies[].kind</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>ComputePolicy</code></td>
					<td>Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to.</td>
			</tr>
			<tr>
					<td><code>spec.policies[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;compute policy&gt;</code></td>
					<td>Name refers to a unique resource in the current namespace.</td>
			</tr>
			<tr>
					<td><code>spec.powerOffMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>Hard</code>, <code>Soft</code>, <code>TrySoft</code>; default <code>TrySoft</code></td>
					<td>PowerOffMode describes the desired behavior when powering off a VM. There are three, supported power off modes: Hard, Soft, and TrySoft.</td>
			</tr>
			<tr>
					<td><code>spec.powerState</code></td>
					<td>string</td>
					<td></td>
					<td><code>PoweredOff</code>, <code>PoweredOn</code>, <code>Suspended</code></td>
					<td>PowerState describes the desired power state of a VirtualMachine.&quot; However, once the field is set to a non-empty value, it may no longer be set to an empty value.</td>
			</tr>
			<tr>
					<td><code>spec.promoteDisksMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>Online</code>, <code>Offline</code>, <code>Disabled</code>; default <code>Online</code></td>
					<td>PromoteDisksMode describes the mode used to promote a VM&rsquo;s delta disks to full disks. The available modes are: - Disabled &ndash; Do not promote disks.</td>
			</tr>
			<tr>
					<td><code>spec.readinessProbe</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{guestInfo: [{key: guestinfo.ready, value: &quot;true&quot;}], tcpSocket: {host: 10.200.0.10, ...}}</code></td>
					<td>ReadinessProbe describes a probe used to determine the VM&rsquo;s ready state.</td>
			</tr>
			<tr>
					<td><code>spec.readinessProbe.guestHeartbeat</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{thresholdStatus: green}</code></td>
					<td>GuestHeartbeat specifies an action involving the guest heartbeat status.</td>
			</tr>
			<tr>
					<td><code>spec.readinessProbe.guestHeartbeat.thresholdStatus</code></td>
					<td>string</td>
					<td></td>
					<td><code>yellow</code>, <code>green</code>; default <code>green</code></td>
					<td>ThresholdStatus is the value that the guest heartbeat status must be at or above to be considered successful.</td>
			</tr>
			<tr>
					<td><code>spec.readinessProbe.guestInfo</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: guestinfo.ready, value: &quot;true&quot;}]</code></td>
					<td>GuestInfo specifies an action involving key/value pairs from GuestInfo.</td>
			</tr>
			<tr>
					<td><code>spec.readinessProbe.guestInfo[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>guestinfo.ready</code></td>
					<td>Key is the name of the GuestInfo key. The key is automatically prefixed with &ldquo;guestinfo.&rdquo; before being evaluated.</td>
			</tr>
			<tr>
					<td><code>spec.readinessProbe.guestInfo[].value</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&quot;true&quot;</code></td>
					<td>Value is a regular expression that is matched against the value of the specified key. An empty value is the equivalent of &ldquo;match any&rdquo; or &ldquo;.*&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.readinessProbe.periodSeconds</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>10</code></td>
					<td>PeriodSeconds specifics how often (in seconds) to perform the probe. Defaults to 10 seconds. Minimum value is 1.</td>
			</tr>
			<tr>
					<td><code>spec.readinessProbe.tcpSocket</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{host: 10.200.0.10, port: 22}</code></td>
					<td>TCPSocket specifies an action involving a TCP port. Deprecated: The TCPSocket action requires network connectivity that is not supported in all environments.</td>
			</tr>
			<tr>
					<td><code>spec.readinessProbe.tcpSocket.host</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10.200.0.10</code></td>
					<td>Host is an optional host name to connect to. Host defaults to the VM IP.</td>
			</tr>
			<tr>
					<td><code>spec.readinessProbe.tcpSocket.port</code></td>
					<td>int or string</td>
					<td>yes</td>
					<td>e.g. <code>22</code></td>
					<td>Port specifies a number or name of the port to access on the VM. If the format of port is a number, it must be in the range 1 to 65535.</td>
			</tr>
			<tr>
					<td><code>spec.readinessProbe.timeoutSeconds</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>10</code></td>
					<td>TimeoutSeconds specifies a number of seconds after which the probe times out. Defaults to 10 seconds. Minimum value is 1.</td>
			</tr>
			<tr>
					<td><code>spec.reserved</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{resourcePolicyName: &lt;resource policy&gt;}</code></td>
					<td>Reserved describes a set of VM configuration options reserved for system use.</td>
			</tr>
			<tr>
					<td><code>spec.reserved.resourcePolicyName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&lt;resource policy&gt;</code></td>
					<td></td>
			</tr>
			<tr>
					<td><code>spec.restartMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>Hard</code>, <code>Soft</code>, <code>TrySoft</code>; default <code>TrySoft</code></td>
					<td>RestartMode describes the desired behavior for restarting a VM when spec.nextRestartTime is set to &ldquo;now&rdquo; (case-insensitive).</td>
			</tr>
			<tr>
					<td><code>spec.storageClass</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>vsan-default-storage-policy</code></td>
					<td>StorageClass describes the name of a Kubernetes StorageClass resource used to configure this VM&rsquo;s storage-related attributes.</td>
			</tr>
			<tr>
					<td><code>spec.suspendMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>Hard</code>, <code>Soft</code>, <code>TrySoft</code>; default <code>TrySoft</code></td>
					<td>SuspendMode describes the desired behavior when suspending a VM. There are three, supported suspend modes: Hard, Soft, and TrySoft.</td>
			</tr>
			<tr>
					<td><code>spec.volumes</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: data, persistentVolumeClaim: {claimName: web-01-data, ...}}]</code></td>
					<td>Volumes describes a list of volumes that can be mounted to the VM.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].applicationType</code></td>
					<td>string</td>
					<td></td>
					<td><code>OracleRAC</code>, <code>MicrosoftWSFC</code></td>
					<td>ApplicationType describes the type of application for which this volume is intended to be used.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].controllerBusNumber</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>0</code></td>
					<td>ControllerBusNumber describes the bus number of the controller to which this volume should be attached.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].controllerType</code></td>
					<td>string</td>
					<td></td>
					<td><code>IDE</code>, <code>NVME</code>, <code>SCSI</code>, <code>SATA</code></td>
					<td>ControllerType describes the type of the controller to which this volume should be attached.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].diskMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>IndependentNonPersistent</code>, <code>IndependentPersistent</code>, <code>NonPersistent</code>, <code>Persistent</code></td>
					<td>DiskMode describes the desired mode to use when attaching the volume.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>data</code></td>
					<td>Name represents the volume&rsquo;s name. Must be a DNS_LABEL and unique within the VM.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].persistentVolumeClaim</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{claimName: web-01-data, instanceVolumeClaim: {size: 50Gi, ...}}</code></td>
					<td>PersistentVolumeClaim represents a reference to a PersistentVolumeClaim in the same namespace.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].persistentVolumeClaim.claimName</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>web-01-data</code></td>
					<td>claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].persistentVolumeClaim.instanceVolumeClaim</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{size: 50Gi, storageClass: vsan-default-storage-policy}</code></td>
					<td>InstanceVolumeClaim is set if the PVC is backed by instance storage.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].persistentVolumeClaim.instanceVolumeClaim.size</code></td>
					<td>int or string</td>
					<td>yes</td>
					<td>e.g. <code>50Gi</code></td>
					<td>Size is the size of the requested instance storage volume.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].persistentVolumeClaim.instanceVolumeClaim.storageClass</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>vsan-default-storage-policy</code></td>
					<td>StorageClass is the name of the Kubernetes StorageClass that provides the backing storage for this instance storage volume.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].persistentVolumeClaim.readOnly</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>readOnly Will force the ReadOnly setting in VolumeMounts. Default false.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].removable</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>true</code></td>
					<td>Removable describes whether or not this volume may be removed from spec.volumes.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].sharingMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>MultiWriter</code>, <code>None</code></td>
					<td>SharingMode describes the volume&rsquo;s desired sharing mode. When applicationType=OracleRAC, this field defaults to MultiWriter.</td>
			</tr>
			<tr>
					<td><code>spec.volumes[].unitNumber</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>1</code></td>
					<td>UnitNumber describes the desired unit number for attaching the volume to a storage controller. When omitted, the next available unit number of the selected controller is used.</td>
			</tr>
	</tbody>
</table>

</details></p>

<p>Minimal:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">vm1</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">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">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.namespace.id}</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">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">vmoperator.vmware.com/v1alpha5</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></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">metadata</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">web-01</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">labels</span><span class="p">:</span><span class="w"> </span>{<span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">web}</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">className</span><span class="p">:</span><span class="w"> </span><span class="l">best-effort-small</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">imageName</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-24.04-server-cloudimg-amd64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">storageClass</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">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">conditions</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">VirtualMachineGuestNetworkConfigSynced</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">status</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;True&#34;</span><span class="w">
</span></span></span></code></pre></div><p><strong>Recipes</strong></p>
<ul>
<li>
<p><em>A Linux user with an SSH key and a password</em>, inline cloud-init. The
password is <strong>not</strong> a string here: <code>passwd</code> and <code>hashed_passwd</code> reference
a key in a Secret, and a plain string fails with <code>cannot restore struct from: string</code>. The hash can come from <a href="#utilpasswordentry">Util.PasswordEntry</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><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">cloudInit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">cloudConfig</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">users</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">ops</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">sudo</span><span class="p">:</span><span class="w"> </span><span class="l">ALL=(ALL) NOPASSWD:ALL</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">lock_passwd</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">hashed_passwd</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">web-pw         </span><span class="w"> </span><span class="c"># a Secret in the namespace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">ops-passwd      </span><span class="w"> </span><span class="c"># holding ${resource.pw.sha512crypt}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">ssh_authorized_keys</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span>- <span class="l">ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOkJfr8Q3cNq ops@example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">runcmd</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span>- <span class="p">[</span><span class="l">systemctl, enable, --now, ssh]</span><span class="w">
</span></span></span></code></pre></div><p>We logged in through a load balancer as <code>ops</code> with that key; <code>sudo</code> needed
no password and the shadow entry held the <code>$6$</code> hash.</p>
</li>
<li>
<p><em>Windows, or a long first-boot script:</em> keep the whole cloud-config in a
Secret and point <code>rawCloudConfig</code> at it. Our jump hosts run cloudbase-init
this way:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><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">cloudInit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">rawCloudConfig</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">jump-bootstrap    </span><span class="w"> </span><span class="c"># the Secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">user-data          </span><span class="w"> </span><span class="c"># the key inside it</span><span class="w">
</span></span></span></code></pre></div></li>
<li>
<p><em>A static address on a VPC subnet.</em> With cloud-init the DNS servers go on
the interface:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><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">hostName</span><span class="p">:</span><span class="w"> </span><span class="l">dc01</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></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">eth0</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 class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="nt">crd.nsx.vmware.com/v1alpha1, kind</span><span class="p">:</span><span class="w"> </span><span class="nt">Subnet, name</span><span class="p">:</span><span class="w"> </span><span class="l">sn-mgmt}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">addresses</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;172.30.0.34/27&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">gateway4</span><span class="p">:</span><span class="w"> </span><span class="m">172.30.0.33</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">nameservers</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="m">172.30.0.34</span><span class="p">]</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">cloudInit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">rawCloudConfig</span><span class="p">:</span><span class="w"> </span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="nt">dc-bootstrap, key</span><span class="p">:</span><span class="w"> </span><span class="l">user-data}</span><span class="w">
</span></span></span></code></pre></div></li>
<li>
<p><em>An address on a subnet without choosing it:</em> name the subnet and leave
<code>addresses</code> out. VM Operator takes one from the subnet and configures the
guest; ours got <code>172.30.0.2</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><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></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">eth0</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 class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="nt">crd.nsx.vmware.com/v1alpha1, kind</span><span class="p">:</span><span class="w"> </span><span class="nt">Subnet, name</span><span class="p">:</span><span class="w"> </span><span class="l">sn-app}</span><span class="w">
</span></span></span></code></pre></div></li>
<li>
<p><em>An extra data disk:</em> a Persistent Volume Claim in the same blueprint, then
the VM below. It arrived in the guest as <code>sdb</code>, 5 GiB, beside the image&rsquo;s
10 GiB <code>sda</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">        </span><span class="nt">volumes</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">data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">persistentVolumeClaim</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">claimName</span><span class="p">:</span><span class="w"> </span><span class="l">web-01-data</span><span class="w">
</span></span></span></code></pre></div></li>
<li>
<p><em>An ISO in the CD-ROM</em>, for an installer (our nested hosts boot the ESXi
installer this way). <code>guestID</code> becomes mandatory:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">        </span><span class="nt">guestID</span><span class="p">:</span><span class="w"> </span><span class="l">vmkernel9Guest</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></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">cdrom0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span>{<span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="nt">VirtualMachineImage, name</span><span class="p">:</span><span class="w"> </span><span class="l">vmi-e400a813bbd5d52a5}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">connected</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">allowGuestControl</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div></li>
<li>
<p><em>A bigger boot disk than the image&rsquo;s:</em> <code>advanced.bootDiskCapacity: 80Gi</code>.
The guest still has to grow its partition.</p>
</li>
<li>
<p><em>Keep VMs apart.</em> Affinity rules only work for members of a
<a href="#virtual-machine-group">Virtual Machine Group</a>; on a VM without
<code>groupName</code> the Supervisor refuses them (<code>spec.groupName: Required value: when setting affinity</code>). Our two web VMs with this rule landed on
different hosts:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">        </span><span class="nt">groupName</span><span class="p">:</span><span class="w"> </span><span class="l">web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">affinity</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">vmAntiAffinity</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">preferredDuringSchedulingPreferredDuringExecution</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span>- <span class="nt">labelSelector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w"> </span>{<span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">web}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">topologyKey</span><span class="p">:</span><span class="w"> </span><span class="l">kubernetes.io/hostname</span><span class="w">
</span></span></span></code></pre></div></li>
</ul>
<p><strong>Status worth reading:</strong> <code>status.network.primaryIP4</code> (after the wait above),
<code>status.powerState</code>, <code>status.nodeName</code> (the ESXi host), <code>status.zone</code>,
<code>status.instanceUUID</code>, <code>status.biosUUID</code>, <code>status.hardwareVersion</code>, and
<code>status.volumes[]</code> with <code>attached</code> per disk.</p>
<p><strong>Gotchas</strong></p>
<ul>
<li>DNS settings depend on the bootstrap provider: <code>spec.network.nameservers</code>
works only with LinuxPrep and Sysprep, the per-interface <code>nameservers</code> only
with CloudInit and Sysprep, and a VM with no bootstrap can have neither.
The Supervisor says which: <code>nameservers is available only with the following bootstrap providers: ...</code>.</li>
<li><code>promoteDisksMode</code> defaults to <code>Online</code>: after a fast deploy from a linked
clone, VM Operator copies the disks into full disks while the VM runs. For
a large image that is real I/O; <code>Disabled</code> keeps the linked clone and
deploys faster. Our Windows jump host was ready in 15.1 minutes with
<code>Disabled</code> against 17.6 with the default. VMs with snapshots cannot
promote online.</li>
<li>The first deployment of a new image into a VPC can fail with an NSX
<code>503638</code> error while the image is still being cached; a retry succeeds.</li>
<li>Changing <code>className</code> resizes the VM; changing <code>manifest</code> in a new blueprint
version recreates it.</li>
</ul>
<h2 id="virtual-machine-group">Virtual Machine Group</h2>
<p><code>CCI.Supervisor.Resource</code> with <code>apiVersion: vmoperator.vmware.com/v1alpha5</code>,
<code>kind: VirtualMachineGroup</code>. A set of VMs placed and powered as one: a boot
order with delays between steps, and one power state for all of them.</p>
<table>
	<thead>
			<tr>
					<th><code>spec</code> field</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>bootOrder[]</code></td>
					<td>Steps, in order. Each has <code>members[]</code> (<code>kind</code>: <code>VirtualMachine</code> default or <code>VirtualMachineGroup</code>; <code>name</code>) and <code>powerOnDelay</code> before the step.</td>
			</tr>
			<tr>
					<td><code>powerState</code></td>
					<td><code>PoweredOn</code> (default), <code>PoweredOff</code>, <code>Suspended</code>, applied to every member.</td>
			</tr>
			<tr>
					<td><code>powerOffMode</code>, <code>suspendMode</code></td>
					<td><code>TrySoft</code> (default), <code>Soft</code>, <code>Hard</code>.</td>
			</tr>
			<tr>
					<td><code>groupName</code></td>
					<td>A parent group, to nest groups.</td>
			</tr>
			<tr>
					<td><code>nextForcePowerStateSyncTime</code></td>
					<td><code>now</code> pushes the group&rsquo;s power state to every member again.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (10)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>spec.bootOrder</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{members: [{name: web-01, kind: VirtualMachine}], powerOnDelay: 30s}]</code></td>
					<td>BootOrder describes the boot sequence for this group members. Each boot order contains a set of members that will be powered on simultaneously, with an optional delay before powering on.</td>
			</tr>
			<tr>
					<td><code>spec.bootOrder[].members</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: web-01, kind: VirtualMachine}]</code></td>
					<td>Members describes the names of VirtualMachine or VirtualMachineGroup objects that are members of this boot order group.</td>
			</tr>
			<tr>
					<td><code>spec.bootOrder[].members[].kind</code></td>
					<td>string</td>
					<td></td>
					<td><code>VirtualMachine</code>, <code>VirtualMachineGroup</code>; default <code>VirtualMachine</code></td>
					<td>Kind is the kind of member of this group, which can be either VirtualMachine or VirtualMachineGroup. If omitted, it defaults to VirtualMachine.</td>
			</tr>
			<tr>
					<td><code>spec.bootOrder[].members[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>web-01</code></td>
					<td>Name is the name of member of this group.</td>
			</tr>
			<tr>
					<td><code>spec.bootOrder[].powerOnDelay</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>30s</code></td>
					<td>PowerOnDelay is the amount of time to wait before powering on all the members of this boot order group.</td>
			</tr>
			<tr>
					<td><code>spec.groupName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&lt;parent group&gt;</code></td>
					<td>GroupName describes the name of the group that this group belongs to.</td>
			</tr>
			<tr>
					<td><code>spec.nextForcePowerStateSyncTime</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>now</code></td>
					<td>NextForcePowerStateSyncTime may be used to force sync the power state of the group to all of its members, by setting the value of this field to &ldquo;now&rdquo; (case-insensitive).</td>
			</tr>
			<tr>
					<td><code>spec.powerOffMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>Hard</code>, <code>Soft</code>, <code>TrySoft</code></td>
					<td>PowerOffMode describes the desired behavior when powering off a VM Group. Refer to the VirtualMachine.PowerOffMode field for more details.</td>
			</tr>
			<tr>
					<td><code>spec.powerState</code></td>
					<td>string</td>
					<td></td>
					<td><code>PoweredOff</code>, <code>PoweredOn</code>, <code>Suspended</code></td>
					<td>PowerState describes the desired power state of a VirtualMachineGroup.</td>
			</tr>
			<tr>
					<td><code>spec.suspendMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>Hard</code>, <code>Soft</code>, <code>TrySoft</code></td>
					<td>SuspendMode describes the desired behavior when suspending a VM Group. Refer to the VirtualMachine.SuspendMode field for more details.</td>
			</tr>
	</tbody>
</table>

</details></p>

<p>Recipe, one VM before the next, thirty seconds apart:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">appGroup</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">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">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.namespace.id}</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">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">vmoperator.vmware.com/v1alpha5</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">VirtualMachineGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">app}</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">bootOrder</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">members</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="l">web-01}]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">members</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="l">web-02}]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">powerOnDelay</span><span class="p">:</span><span class="w"> </span><span class="l">30s</span><span class="w">
</span></span></span></code></pre></div><p>Each member names the group in <code>spec.groupName: app</code> and depends on the group
resource (<code>dependsOn: [appGroup]</code>), because the group is referenced only by
name.</p>
<p><strong>Status worth reading:</strong> <code>status.members[]</code> (each member&rsquo;s power state and
placement), <code>status.conditions</code>.</p>
<p><strong>Gotchas</strong></p>
<ul>
<li>A member of a later step stays off until every member of the earlier steps
is on. When our first test&rsquo;s <code>web-01</code> failed to create, <code>web-02</code> sat
powered off for good.</li>
<li>A VM in a group doesn&rsquo;t place itself; the group places its members.</li>
</ul>
<h2 id="virtual-machine-service">Virtual Machine Service</h2>
<p><code>CCI.Supervisor.Resource</code> with <code>apiVersion: vmoperator.vmware.com/v1alpha5</code>,
<code>kind: VirtualMachineService</code>. A Kubernetes-style service in front of VMs,
selected by label. With <code>type: LoadBalancer</code>, the VPC&rsquo;s load balancer gives
it an external address. That&rsquo;s how a VM on a private subnet is reached from
outside.</p>
<table>
	<thead>
			<tr>
					<th><code>spec</code> field</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>type</code></td>
					<td><strong>Required.</strong> <code>LoadBalancer</code> or <code>ClusterIP</code>. The API lists <code>ExternalName</code> too; the VM Operator documentation says it isn&rsquo;t supported.</td>
			</tr>
			<tr>
					<td><code>selector</code></td>
					<td>Labels of the VMs behind it.</td>
			</tr>
			<tr>
					<td><code>ports[]</code></td>
					<td><code>name</code>, <code>port</code>, <code>targetPort</code>, <code>protocol</code> (<code>TCP</code>, <code>UDP</code>, <code>SCTP</code>).</td>
			</tr>
			<tr>
					<td><code>loadBalancerSourceRanges[]</code></td>
					<td>Source CIDRs allowed to reach a <code>LoadBalancer</code> service.</td>
			</tr>
			<tr>
					<td><code>loadBalancerIP</code>, <code>clusterIp</code>, <code>externalName</code></td>
					<td>A requested address, a fixed cluster IP, a DNS name.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (11)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>spec.clusterIp</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10.96.0.20</code></td>
					<td>ClusterIP is the IP address of the service and is usually assigned randomly by the master.</td>
			</tr>
			<tr>
					<td><code>spec.externalName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>web.example.com</code></td>
					<td>ExternalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved.</td>
			</tr>
			<tr>
					<td><code>spec.loadBalancerIP</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>192.168.144.20</code></td>
					<td>LoadBalancer will get created with the IP specified in this field.</td>
			</tr>
			<tr>
					<td><code>spec.loadBalancerSourceRanges</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[10.0.0.0/8]</code></td>
					<td>LoadBalancerSourceRanges is an array of IP addresses in the format of CIDRs, for example: 103.21.244.0/22 and 10.0.0.0/24.</td>
			</tr>
			<tr>
					<td><code>spec.ports</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: rdp, port: 3389}]</code></td>
					<td>Ports specifies a list of VirtualMachineServicePort to expose with this VirtualMachineService. Each of these ports will be an accessible network entry point to access this service by.</td>
			</tr>
			<tr>
					<td><code>spec.ports[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>rdp</code></td>
					<td>Name describes the name to be used to identify this VirtualMachineServicePort.</td>
			</tr>
			<tr>
					<td><code>spec.ports[].port</code></td>
					<td>integer</td>
					<td>yes</td>
					<td>e.g. <code>3389</code></td>
					<td>Port describes the external port that will be exposed by the service.</td>
			</tr>
			<tr>
					<td><code>spec.ports[].protocol</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>TCP</code></td>
					<td>Protocol describes the Layer 4 transport protocol for this port. Supports &ldquo;TCP&rdquo;, &ldquo;UDP&rdquo;, and &ldquo;SCTP&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.ports[].targetPort</code></td>
					<td>integer</td>
					<td>yes</td>
					<td>e.g. <code>3389</code></td>
					<td>TargetPort describes the internal port open on a VirtualMachine that should be mapped to the external Port.</td>
			</tr>
			<tr>
					<td><code>spec.selector</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{app: web}</code></td>
					<td>Selector specifies a map of key-value pairs, also known as a Label Selector, that is used to match this VirtualMachineService with the set of VirtualMachines that should back this VirtualMachineService.</td>
			</tr>
			<tr>
					<td><code>spec.type</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>LoadBalancer</code></td>
					<td>Type specifies a desired VirtualMachineServiceType for this VirtualMachineService. Supported types are ClusterIP, LoadBalancer, ExternalName.</td>
			</tr>
	</tbody>
</table>

</details></p>

<p>Recipe, RDP to a jump host, the only way into our labs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">jumpAccess</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">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">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.namespace.id}</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">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">vmoperator.vmware.com/v1alpha5</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">VirtualMachineService</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">jump-access}</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">type</span><span class="p">:</span><span class="w"> </span><span class="l">LoadBalancer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">selector</span><span class="p">:</span><span class="w"> </span>{<span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">jump}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">ports</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">rdp, port</span><span class="p">:</span><span class="w"> </span><span class="nt">3389, targetPort</span><span class="p">:</span><span class="w"> </span><span class="nt">3389, protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">loadBalancerSourceRanges</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="m">10.0.0.0</span><span class="l">/8]</span><span class="w">
</span></span></span></code></pre></div><p><strong>Status worth reading:</strong> <code>status.loadBalancer.ingress[0].ip</code>, the external
address.</p>
<p><strong>Gotchas</strong></p>
<ul>
<li><code>LoadBalancer</code> needs the VPC&rsquo;s load balancer. VCF Automation waits for the
address by itself, so in a VPC without one the request stays in progress
until it times out. A VPC made by a blueprint never has one; see <a href="#vpc">VPC</a>.</li>
<li>A service with several VMs behind it spreads connections across them: our
SSH sessions alternated between the two web VMs.</li>
<li>The palette writes <code>v1alpha3</code>; we use <code>v1alpha5</code>, the Supervisor&rsquo;s stored
version. Both are served.</li>
</ul>
<h2 id="subnet">Subnet</h2>
<p><code>CCI.Supervisor.Resource</code> with <code>apiVersion: crd.nsx.vmware.com/v1alpha1</code>,
<code>kind: Subnet</code>. A subnet of the namespace&rsquo;s VPC: a layer-2 segment with its
own address range, for VMs that need a network of their own.</p>
<table>
	<thead>
			<tr>
					<th><code>spec</code> field</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>accessMode</code></td>
					<td><code>Private</code> (default): routed inside the VPC only. <code>PrivateTGW</code>: reachable from other VPCs through the transit gateway. <code>Public</code>: from the external network.</td>
			</tr>
			<tr>
					<td><code>ipv4SubnetSize</code></td>
					<td>Addresses in the subnet, default 64.</td>
			</tr>
			<tr>
					<td><code>ipAddresses[]</code></td>
					<td>Specific CIDRs instead of a size.</td>
			</tr>
			<tr>
					<td><code>subnetDHCPConfig.mode</code></td>
					<td><code>DHCPDeactivated</code> (default), <code>DHCPServer</code>, <code>DHCPRelay</code>; <code>dhcpServerAdditionalConfig.reservedIPRanges</code> keeps ranges out of the pool.</td>
			</tr>
			<tr>
					<td><code>advancedConfig</code></td>
					<td><code>staticIPAllocation.enabled</code>, <code>connectivityState</code> (<code>Connected</code> default, <code>Disconnected</code>), <code>gatewayAddresses</code>, <code>dhcpServerAddresses</code>.</td>
			</tr>
			<tr>
					<td><code>vpcName</code></td>
					<td>The VPC, when it isn&rsquo;t the namespace&rsquo;s own.</td>
			</tr>
			<tr>
					<td><code>vlanConnectionName</code></td>
					<td>A subnet backed by a distributed VLAN connection; not in the designer&rsquo;s form.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (15)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>spec.accessMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>Private</code>, <code>Public</code>, <code>PrivateTGW</code>, <code>L2Only</code></td>
					<td>Access mode of Subnet, accessible only from within VPC or from outside VPC.</td>
			</tr>
			<tr>
					<td><code>spec.advancedConfig</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{dhcpServerAddresses: [10.200.0.2/28], gatewayAddresses: [10.200.0.1/28]}</code></td>
					<td>VPC Subnet advanced configuration.</td>
			</tr>
			<tr>
					<td><code>spec.advancedConfig.connectivityState</code></td>
					<td>string</td>
					<td></td>
					<td><code>Connected</code>, <code>Disconnected</code>; default <code>Connected</code></td>
					<td>Connectivity status of the Subnet from other Subnets of the VPC. The default value is &ldquo;Connected&rdquo;.</td>
			</tr>
			<tr>
					<td><code>spec.advancedConfig.dhcpServerAddresses</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[10.200.0.2/28]</code></td>
					<td>DHCPServerAddresses specifies custom DHCP server IP addresses for the Subnet.</td>
			</tr>
			<tr>
					<td><code>spec.advancedConfig.gatewayAddresses</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[10.200.0.1/28]</code></td>
					<td>GatewayAddresses specifies custom gateway IP addresses for the Subnet.</td>
			</tr>
			<tr>
					<td><code>spec.advancedConfig.staticIPAllocation</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{enabled: true}</code></td>
					<td>Static IP allocation for VPC Subnet Ports.</td>
			</tr>
			<tr>
					<td><code>spec.advancedConfig.staticIPAllocation.enabled</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>Activate or deactivate static IP allocation for VPC Subnet Ports. If the DHCP mode is DHCPDeactivated or not set, its default value is true.</td>
			</tr>
			<tr>
					<td><code>spec.ipAddresses</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[10.200.0.0/28]</code></td>
					<td>Subnet CIDRS.</td>
			</tr>
			<tr>
					<td><code>spec.ipv4SubnetSize</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>32</code></td>
					<td>Size of Subnet based upon estimated workload count.</td>
			</tr>
			<tr>
					<td><code>spec.subnetDHCPConfig</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{dhcpServerAdditionalConfig: {reservedIPRanges: [10.200.0.10-10.200.0.15]}, ...}</code></td>
					<td>DHCP configuration for Subnet.</td>
			</tr>
			<tr>
					<td><code>spec.subnetDHCPConfig.dhcpServerAdditionalConfig</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{reservedIPRanges: [10.200.0.10-10.200.0.15]}</code></td>
					<td>Additional DHCP server config for a VPC Subnet.</td>
			</tr>
			<tr>
					<td><code>spec.subnetDHCPConfig.dhcpServerAdditionalConfig.reservedIPRanges</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[10.200.0.10-10.200.0.15]</code></td>
					<td>Reserved IP ranges. Supported formats include: [&ldquo;192.168.1.1&rdquo;, &ldquo;192.168.1.3-192.168.1.100&rdquo;]</td>
			</tr>
			<tr>
					<td><code>spec.subnetDHCPConfig.mode</code></td>
					<td>string</td>
					<td></td>
					<td><code>DHCPServer</code>, <code>DHCPRelay</code>, <code>DHCPDeactivated</code></td>
					<td>DHCP Mode. DHCPDeactivated will be used if it is not defined. It cannot switch from DHCPDeactivated to DHCPServer or DHCPRelay.</td>
			</tr>
			<tr>
					<td><code>spec.vlanConnectionName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&lt;distributed VLAN connection&gt;</code></td>
					<td>Distributed VLAN Connection name.</td>
			</tr>
			<tr>
					<td><code>spec.vpcName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>${resource.vpc.name}</code></td>
					<td>VPC name of the Subnet.</td>
			</tr>
	</tbody>
</table>

</details></p>

<p>Minimal, our lab&rsquo;s management subnet:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">snMgmt</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">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">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.namespace.id}</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">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">crd.nsx.vmware.com/v1alpha1</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">Subnet</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">sn-mgmt}</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 class="nt">accessMode</span><span class="p">:</span><span class="w"> </span><span class="nt">Private, ipv4SubnetSize</span><span class="p">:</span><span class="w"> </span><span class="m">32</span>}<span class="w">
</span></span></span></code></pre></div><p>A VM joins it by name, in an interface:
<code>network: {apiVersion: crd.nsx.vmware.com/v1alpha1, kind: Subnet, name: sn-mgmt}</code>.</p>
<p><strong>Status worth reading:</strong> <code>status.networkAddresses</code>, <code>status.gatewayAddresses</code>,
<code>status.conditions</code> (<code>Realized</code>).</p>
<p><strong>Gotchas</strong></p>
<ul>
<li>DHCP is off by default. VMs still get addresses: VM Operator takes one
from the subnet and hands it to the guest through the bootstrap provider.</li>
<li>Subnets are NSX objects of the VPC. After a deployment is deleted they can
outlive it for a few minutes; wait until the VPC lists none before reusing
the addresses.</li>
</ul>
<h2 id="persistent-volume-claim">Persistent Volume Claim</h2>
<p><code>CCI.Supervisor.Resource</code> with <code>apiVersion: v1</code>,
<code>kind: PersistentVolumeClaim</code>. A disk from a storage class, for a VM&rsquo;s
<code>volumes</code> or a pod.</p>
<table>
	<thead>
			<tr>
					<th><code>spec</code> field</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>storageClassName</code></td>
					<td>A storage class the namespace has.</td>
			</tr>
			<tr>
					<td><code>resources.requests.storage</code></td>
					<td>The size: <code>100Gi</code>.</td>
			</tr>
			<tr>
					<td><code>accessModes[]</code></td>
					<td><code>ReadWriteOnce</code> for a VM disk; <code>ReadWriteMany</code> where the storage supports it.</td>
			</tr>
			<tr>
					<td><code>volumeMode</code></td>
					<td><code>Filesystem</code> or <code>Block</code>.</td>
			</tr>
			<tr>
					<td><code>dataSource</code>, <code>dataSourceRef</code></td>
					<td>Restore from a snapshot or clone another claim.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (23)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>spec.accessModes</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[ReadWriteOnce]</code></td>
					<td>accessModes contains the desired access modes the volume should have.</td>
			</tr>
			<tr>
					<td><code>spec.dataSource</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{apiGroup: snapshot.storage.k8s.io, kind: &lt;kind&gt;}</code></td>
					<td>TypedLocalObjectReference contains enough information to let you locate the typed referenced object inside the same namespace.</td>
			</tr>
			<tr>
					<td><code>spec.dataSource.apiGroup</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>snapshot.storage.k8s.io</code></td>
					<td>APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group.</td>
			</tr>
			<tr>
					<td><code>spec.dataSource.kind</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;kind&gt;</code></td>
					<td>Kind is the type of resource being referenced</td>
			</tr>
			<tr>
					<td><code>spec.dataSource.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;name&gt;</code></td>
					<td>Name is the name of resource being referenced</td>
			</tr>
			<tr>
					<td><code>spec.dataSourceRef</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{apiGroup: snapshot.storage.k8s.io, namespace: ns-source}</code></td>
					<td>TypedObjectReference contains enough information to let you locate the typed referenced object</td>
			</tr>
			<tr>
					<td><code>spec.dataSourceRef.apiGroup</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>snapshot.storage.k8s.io</code></td>
					<td>APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group.</td>
			</tr>
			<tr>
					<td><code>spec.dataSourceRef.kind</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;kind&gt;</code></td>
					<td>Kind is the type of resource being referenced</td>
			</tr>
			<tr>
					<td><code>spec.dataSourceRef.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;name&gt;</code></td>
					<td>Name is the name of resource being referenced</td>
			</tr>
			<tr>
					<td><code>spec.dataSourceRef.namespace</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>ns-source</code></td>
					<td>Namespace is the namespace of resource being referenced Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to &hellip;</td>
			</tr>
			<tr>
					<td><code>spec.resources</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{limits: {storage: 20Gi}, requests: {storage: 20Gi}}</code></td>
					<td>VolumeResourceRequirements describes the storage resource requirements for a volume.</td>
			</tr>
			<tr>
					<td><code>spec.resources.limits</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{storage: 20Gi}</code></td>
					<td>Limits describes the maximum amount of compute resources allowed.</td>
			</tr>
			<tr>
					<td><code>spec.resources.requests</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{storage: 20Gi}</code></td>
					<td>Requests describes the minimum amount of compute resources required.</td>
			</tr>
			<tr>
					<td><code>spec.selector</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{matchExpressions: [{key: app, operator: In}], matchLabels: {tier: fast}}</code></td>
					<td>A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.selector.matchExpressions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: app, operator: In}]</code></td>
					<td>matchExpressions is a list of label selector requirements. The requirements are ANDed.</td>
			</tr>
			<tr>
					<td><code>spec.selector.matchExpressions[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>app</code></td>
					<td>key is the label key that the selector applies to.</td>
			</tr>
			<tr>
					<td><code>spec.selector.matchExpressions[].operator</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>In</code></td>
					<td>operator represents a key&rsquo;s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.</td>
			</tr>
			<tr>
					<td><code>spec.selector.matchExpressions[].values</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[fast]</code></td>
					<td>values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty.</td>
			</tr>
			<tr>
					<td><code>spec.selector.matchLabels</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{tier: fast}</code></td>
					<td>matchLabels is a map of {key,value} pairs.</td>
			</tr>
			<tr>
					<td><code>spec.storageClassName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>vsan-default-storage-policy</code></td>
					<td>storageClassName is the name of the StorageClass required by the claim.</td>
			</tr>
			<tr>
					<td><code>spec.volumeAttributesClassName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&lt;volume attributes class&gt;</code></td>
					<td>volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim.</td>
			</tr>
			<tr>
					<td><code>spec.volumeMode</code></td>
					<td>string</td>
					<td></td>
					<td><code>Block</code>, <code>Filesystem</code></td>
					<td>volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec.</td>
			</tr>
			<tr>
					<td><code>spec.volumeName</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&lt;existing PersistentVolume&gt;</code></td>
					<td>volumeName is the binding reference to the PersistentVolume backing this claim.</td>
			</tr>
	</tbody>
</table>

</details></p>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">dataDisk</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">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">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.namespace.id}</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">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</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">PersistentVolumeClaim</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">web-01-data}</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">accessModes</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">ReadWriteOnce]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">storageClassName</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">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">storage</span><span class="p">:</span><span class="w"> </span><span class="l">5Gi</span><span class="w">
</span></span></span></code></pre></div><p><strong>Status worth reading:</strong> <code>status.phase</code> (<code>Bound</code>), <code>status.capacity.storage</code>.</p>
<p><strong>Gotchas</strong></p>
<ul>
<li>The designer&rsquo;s form calls the field <code>accessMode</code>; the Supervisor refuses
that spelling (<code>unknown field &quot;spec.accessMode&quot;</code>).</li>
<li>A <code>-latebinding</code> storage class (WaitForFirstConsumer) puts no constraint on
where the VM lands; our nested hosts&rsquo; vSAN capacity disks use one.</li>
</ul>
<h2 id="secret">Secret</h2>
<p><code>CCI.Supervisor.Resource</code> with <code>apiVersion: v1</code>, <code>kind: Secret</code>. Key/value
data in the namespace. In a blueprint it mostly carries a VM&rsquo;s cloud-init or
Sysprep data, or a password a VM&rsquo;s <code>cloudConfig</code> references.</p>
<table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>stringData</code></td>
					<td>Plain values; the Supervisor encodes them. The easy one in a blueprint.</td>
			</tr>
			<tr>
					<td><code>data</code></td>
					<td>Base64-encoded values.</td>
			</tr>
			<tr>
					<td><code>type</code></td>
					<td><code>Opaque</code> for your own data; <code>kubernetes.io/tls</code> and the other standard types.</td>
			</tr>
			<tr>
					<td><code>immutable</code></td>
					<td><code>true</code> stops changes after creation.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (4)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>data</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{password: &lt;base64&gt;}</code></td>
					<td>Data contains the secret data. Each key must consist of alphanumeric characters, &lsquo;-&rsquo;, &lsquo;_&rsquo; or &lsquo;.&rsquo;.</td>
			</tr>
			<tr>
					<td><code>immutable</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>Immutable, if set to true, ensures that data stored in the Secret cannot be updated (only object metadata can be modified).</td>
			</tr>
			<tr>
					<td><code>stringData</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{password: ${input.password}}</code></td>
					<td>stringData allows specifying non-binary secret data in string form. It is provided as a write-only input field for convenience.</td>
			</tr>
			<tr>
					<td><code>type</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>Opaque</code></td>
					<td>Used to facilitate programmatic handling of secret data.</td>
			</tr>
	</tbody>
</table>

</details></p>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">jumpConfig</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">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">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.namespace.id}</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">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</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">Secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">jump-bootstrap}</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">Opaque</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">stringData</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">user-data</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            #cloud-config
</span></span></span><span class="line"><span class="cl"><span class="sd">            users:
</span></span></span><span class="line"><span class="cl"><span class="sd">              - name: student
</span></span></span><span class="line"><span class="cl"><span class="sd">                passwd: ${input.jumpPassword}</span><span class="w">
</span></span></span></code></pre></div><p><strong>Gotcha:</strong> an input marked <code>encrypted: true</code> stays hidden in the request,
but whatever a Secret holds can be read by anyone allowed to read Secrets in
the namespace.</p>
<h2 id="kubernetes-cluster">Kubernetes Cluster</h2>
<p><code>CCI.Supervisor.Resource</code> with <code>apiVersion: cluster.x-k8s.io/v1beta1</code>,
<code>kind: Cluster</code>. A VKS cluster, described by a ClusterClass topology. The
designer&rsquo;s schema stops at <code>topology.variables</code>. What the variables can be
comes from the ClusterClass: <code>builtin-generic-v3.6.0</code> on our platform.</p>
<table>
	<thead>
			<tr>
					<th><code>spec</code> field</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>clusterNetwork</code></td>
					<td><strong>Required.</strong> <code>pods.cidrBlocks</code>, <code>services.cidrBlocks</code>, <code>serviceDomain</code>. Without <code>services</code> VKS refuses the cluster: <code>spec.ClusterNetwork.Services must be defined</code>.</td>
			</tr>
			<tr>
					<td><code>topology.class</code></td>
					<td><strong>Required.</strong> The ClusterClass.</td>
			</tr>
			<tr>
					<td><code>topology.classNamespace</code></td>
					<td>Where the ClusterClass lives; not in the designer&rsquo;s form. See the gotchas.</td>
			</tr>
			<tr>
					<td><code>topology.version</code></td>
					<td><strong>Required.</strong> A Kubernetes release the Supervisor offers, for example <code>v1.35.5+vmware.1-vkr.1</code>.</td>
			</tr>
			<tr>
					<td><code>topology.controlPlane</code></td>
					<td><code>replicas</code> (1 or 3), <code>metadata</code>, <code>machineHealthCheck</code>, node drain and deletion timeouts.</td>
			</tr>
			<tr>
					<td><code>topology.workers.machineDeployments[]</code></td>
					<td><code>class: node-pool</code> (the only worker class), <code>name</code>, <code>replicas</code>, and <code>variables.overrides[]</code> per pool.</td>
			</tr>
			<tr>
					<td><code>topology.variables[]</code></td>
					<td><code>name</code> and <code>value</code> pairs, below.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (85)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>spec.availabilityGates</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{conditionType: &lt;condition type&gt;, polarity: Positive}]</code></td>
					<td>availabilityGates specifies additional conditions to include when evaluating Cluster Available condition.</td>
			</tr>
			<tr>
					<td><code>spec.availabilityGates[].conditionType</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;condition type&gt;</code></td>
					<td>conditionType refers to a condition with matching type in the Cluster&rsquo;s condition list. If the conditions doesn&rsquo;t exist, it will be treated as unknown.</td>
			</tr>
			<tr>
					<td><code>spec.availabilityGates[].polarity</code></td>
					<td>string</td>
					<td></td>
					<td><code>Positive</code>, <code>Negative</code></td>
					<td>polarity of the conditionType specified in this availabilityGate. Valid values are Positive, Negative and omitted. When omitted, the default behaviour will be Positive.</td>
			</tr>
			<tr>
					<td><code>spec.clusterNetwork</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{pods: {cidrBlocks: [192.168.156.0/20]}, serviceDomain: cluster.local}</code></td>
					<td>clusterNetwork represents the cluster network configuration.</td>
			</tr>
			<tr>
					<td><code>spec.clusterNetwork.apiServerPort</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>6443</code></td>
					<td>apiServerPort specifies the port the API Server should bind to. Defaults to 6443.</td>
			</tr>
			<tr>
					<td><code>spec.clusterNetwork.pods</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{cidrBlocks: [192.168.156.0/20]}</code></td>
					<td>pods is the network ranges from which Pod networks are allocated.</td>
			</tr>
			<tr>
					<td><code>spec.clusterNetwork.pods.cidrBlocks</code></td>
					<td>array of string</td>
					<td>yes</td>
					<td>e.g. <code>[192.168.156.0/20]</code></td>
					<td>cidrBlocks is a list of CIDR blocks.</td>
			</tr>
			<tr>
					<td><code>spec.clusterNetwork.serviceDomain</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>cluster.local</code></td>
					<td>serviceDomain is the domain name for services.</td>
			</tr>
			<tr>
					<td><code>spec.clusterNetwork.services</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{cidrBlocks: [10.96.0.0/12]}</code></td>
					<td>services is the network ranges from which service VIPs are allocated.</td>
			</tr>
			<tr>
					<td><code>spec.clusterNetwork.services.cidrBlocks</code></td>
					<td>array of string</td>
					<td>yes</td>
					<td>e.g. <code>[10.96.0.0/12]</code></td>
					<td>cidrBlocks is a list of CIDR blocks.</td>
			</tr>
			<tr>
					<td><code>spec.controlPlaneEndpoint</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{host: 192.168.144.40, port: 6443}</code></td>
					<td>controlPlaneEndpoint represents the endpoint used to communicate with the control plane.</td>
			</tr>
			<tr>
					<td><code>spec.controlPlaneEndpoint.host</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>192.168.144.40</code></td>
					<td>host is the hostname on which the API server is serving.</td>
			</tr>
			<tr>
					<td><code>spec.controlPlaneEndpoint.port</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>6443</code></td>
					<td>port is the port on which the API server is serving.</td>
			</tr>
			<tr>
					<td><code>spec.controlPlaneRef</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{kind: KubeadmControlPlane, name: lab-vks-cp}</code></td>
					<td>controlPlaneRef is an optional reference to a provider-specific resource that holds the details for provisioning the Control Plane for a Cluster.</td>
			</tr>
			<tr>
					<td><code>spec.controlPlaneRef.apiVersion</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>controlplane.cluster.x-k8s.io/v1beta1</code></td>
					<td>API version of the referent.</td>
			</tr>
			<tr>
					<td><code>spec.controlPlaneRef.fieldPath</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>(set by the platform)</code></td>
					<td>If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].</td>
			</tr>
			<tr>
					<td><code>spec.controlPlaneRef.kind</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>KubeadmControlPlane</code></td>
					<td>Kind of the referent.</td>
			</tr>
			<tr>
					<td><code>spec.controlPlaneRef.name</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>lab-vks-cp</code></td>
					<td>Name of the referent.</td>
			</tr>
			<tr>
					<td><code>spec.controlPlaneRef.namespace</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>ns-lab</code></td>
					<td>Namespace of the referent.</td>
			</tr>
			<tr>
					<td><code>spec.controlPlaneRef.resourceVersion</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>(set by the platform)</code></td>
					<td>Specific resourceVersion to which this reference is made, if any.</td>
			</tr>
			<tr>
					<td><code>spec.controlPlaneRef.uid</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>(set by the platform)</code></td>
					<td>UID of the referent.</td>
			</tr>
			<tr>
					<td><code>spec.infrastructureRef</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{kind: VSphereCluster, name: lab-vks}</code></td>
					<td>infrastructureRef is a reference to a provider-specific resource that holds the details for provisioning infrastructure for a cluster in said provider.</td>
			</tr>
			<tr>
					<td><code>spec.infrastructureRef.apiVersion</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>vmware.infrastructure.cluster.x-k8s.io/v1beta1</code></td>
					<td>API version of the referent.</td>
			</tr>
			<tr>
					<td><code>spec.infrastructureRef.fieldPath</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>(set by the platform)</code></td>
					<td>If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].</td>
			</tr>
			<tr>
					<td><code>spec.infrastructureRef.kind</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>VSphereCluster</code></td>
					<td>Kind of the referent.</td>
			</tr>
			<tr>
					<td><code>spec.infrastructureRef.name</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>lab-vks</code></td>
					<td>Name of the referent.</td>
			</tr>
			<tr>
					<td><code>spec.infrastructureRef.namespace</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>ns-lab</code></td>
					<td>Namespace of the referent.</td>
			</tr>
			<tr>
					<td><code>spec.infrastructureRef.resourceVersion</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>(set by the platform)</code></td>
					<td>Specific resourceVersion to which this reference is made, if any.</td>
			</tr>
			<tr>
					<td><code>spec.infrastructureRef.uid</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>(set by the platform)</code></td>
					<td>UID of the referent.</td>
			</tr>
			<tr>
					<td><code>spec.paused</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>paused can be used to prevent controllers from processing the Cluster and all its associated objects.</td>
			</tr>
			<tr>
					<td><code>spec.topology</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{class: builtin-generic-v3.6.0, classNamespace: vmware-system-vks-public}</code></td>
					<td>topology encapsulates the topology for the cluster.</td>
			</tr>
			<tr>
					<td><code>spec.topology.class</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>builtin-generic-v3.6.0</code></td>
					<td>class is the name of the ClusterClass object to create the topology.</td>
			</tr>
			<tr>
					<td><code>spec.topology.classNamespace</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>vmware-system-vks-public</code></td>
					<td>classNamespace is the namespace of the ClusterClass that should be used for the topology. If classNamespace is empty or not set, it is defaulted to the namespace of the Cluster object.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{replicas: 1, machineHealthCheck: {maxUnhealthy: 40%, nodeStartupTimeout: 10m}}</code></td>
					<td>controlPlane describes the cluster control plane.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.machineHealthCheck</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{maxUnhealthy: 40%, nodeStartupTimeout: 10m}</code></td>
					<td>machineHealthCheck allows to enable, disable and override the MachineHealthCheck configuration in the ClusterClass for this control plane.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.machineHealthCheck.enable</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>enable controls if a MachineHealthCheck should be created for the target machines. If false: No MachineHealthCheck will be created.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.machineHealthCheck.maxUnhealthy</code></td>
					<td>int or string</td>
					<td></td>
					<td>e.g. <code>40%</code></td>
					<td>maxUnhealthy specifies the maximum number of unhealthy machines allowed. Any further remediation is only allowed if at most &ldquo;maxUnhealthy&rdquo; machines selected by &ldquo;selector&rdquo; are not healthy.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.machineHealthCheck.nodeStartupTimeout</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10m</code></td>
					<td>nodeStartupTimeout allows to set the maximum time for MachineHealthCheck to consider a Machine unhealthy if a corresponding Node isn&rsquo;t associated through a <code>Spec.ProviderID</code> field.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.machineHealthCheck.remediationTemplate</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{apiVersion: &lt;group&gt;/&lt;version&gt;, kind: &lt;remediation template kind&gt;, name: &lt;name&gt;}</code></td>
					<td>remediationTemplate is a reference to a remediation template provided by an infrastructure provider.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.machineHealthCheck.unhealthyConditions</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{type: Ready, status: Unknown, timeout: 300s}]</code></td>
					<td>unhealthyConditions contains a list of the conditions that determine whether a node is considered unhealthy. The conditions are combined in a logical OR, i.e.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.machineHealthCheck.unhealthyRange</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>'[1-3]'</code></td>
					<td>unhealthyRange specifies the range of unhealthy machines allowed.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.metadata</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{annotations: {owner: team-a}, labels: {app: web}}</code></td>
					<td>metadata is the metadata applied to the ControlPlane and the Machines of the ControlPlane if the ControlPlaneTemplate referenced by the ClusterClass is machine based.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.metadata.annotations</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{owner: team-a}</code></td>
					<td>annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.metadata.labels</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{app: web}</code></td>
					<td>labels is a map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.nodeDeletionTimeout</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10m</code></td>
					<td>nodeDeletionTimeout defines how long the controller will attempt to delete the Node that the Machine hosts after the Machine is marked for deletion.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.nodeDrainTimeout</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10m</code></td>
					<td>nodeDrainTimeout is the total amount of time that the controller will spend on draining a node. The default value is 0, meaning that the node can be drained without any time limitations.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.nodeVolumeDetachTimeout</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10m</code></td>
					<td>nodeVolumeDetachTimeout is the total amount of time that the controller will spend on waiting for all volumes to be detached.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.readinessGates</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{conditionType: &lt;condition type&gt;, polarity: Positive}]</code></td>
					<td>readinessGates specifies additional conditions to include when evaluating Machine Ready condition. This field can be used e.g.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.readinessGates[].conditionType</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;condition type&gt;</code></td>
					<td>conditionType refers to a condition with matching type in the Machine&rsquo;s condition list. If the conditions doesn&rsquo;t exist, it will be treated as unknown.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.readinessGates[].polarity</code></td>
					<td>string</td>
					<td></td>
					<td><code>Positive</code>, <code>Negative</code></td>
					<td>polarity of the conditionType specified in this readinessGate. Valid values are Positive, Negative and omitted. When omitted, the default behaviour will be Positive.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.replicas</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>1</code></td>
					<td>replicas is the number of control plane nodes.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.variables</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{overrides: [{name: vmClass, value: best-effort-medium}]}</code></td>
					<td>variables can be used to customize the ControlPlane through patches.</td>
			</tr>
			<tr>
					<td><code>spec.topology.controlPlane.variables.overrides</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: vmClass, value: best-effort-medium}]</code></td>
					<td>overrides can be used to override Cluster level variables.</td>
			</tr>
			<tr>
					<td><code>spec.topology.rolloutAfter</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>2026-10-01T22:00:00Z</code></td>
					<td>rolloutAfter performs a rollout of the entire cluster one component at a time, control plane first and then machine deployments.</td>
			</tr>
			<tr>
					<td><code>spec.topology.variables</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: vmClass, value: best-effort-small}]</code></td>
					<td>variables can be used to customize the Cluster through patches. They must comply to the corresponding VariableClasses defined in the ClusterClass.</td>
			</tr>
			<tr>
					<td><code>spec.topology.variables[].definitionFrom</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&lt;patch name&gt;</code></td>
					<td>definitionFrom specifies where the definition of this Variable is from. Deprecated: This field is deprecated, must not be set anymore and is going to be removed in the next apiVersion.</td>
			</tr>
			<tr>
					<td><code>spec.topology.variables[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>vmClass</code></td>
					<td>name of the variable.</td>
			</tr>
			<tr>
					<td><code>spec.topology.variables[].value</code></td>
					<td>any</td>
					<td>yes</td>
					<td>e.g. <code>best-effort-small</code></td>
					<td>value of the variable. Note: the value will be validated against the schema of the corresponding ClusterClassVariable from the ClusterClass.</td>
			</tr>
			<tr>
					<td><code>spec.topology.version</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>v1.35.5+vmware.1-vkr.1</code></td>
					<td>version is the Kubernetes version of the cluster.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{machineDeployments: [{name: np-1, class: node-pool}], machinePools: [{name: mp-1, ...}]}</code></td>
					<td>workers encapsulates the different constructs that form the worker nodes for the cluster.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: np-1, class: node-pool}]</code></td>
					<td>machineDeployments is a list of machine deployments in the cluster.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].class</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>node-pool</code></td>
					<td>class is the name of the MachineDeploymentClass used to create the set of worker nodes.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].failureDomain</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>domain-c9</code></td>
					<td>failureDomain is the failure domain the machines will be created in. Must match a key in the FailureDomains map stored on the cluster object.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].machineHealthCheck</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{enable: true}</code></td>
					<td>machineHealthCheck allows to enable, disable and override the MachineHealthCheck configuration in the ClusterClass for this MachineDeployment.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].metadata</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{labels: {pool: np-1}}</code></td>
					<td>metadata is the metadata applied to the MachineDeployment and the machines of the MachineDeployment. At runtime this metadata is merged with the corresponding metadata from the ClusterClass.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].minReadySeconds</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>10</code></td>
					<td>minReadySeconds is the minimum number of seconds for which a newly created machine should be ready. Defaults to 0 (machine will be considered available as soon as it is ready)</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>np-1</code></td>
					<td>name is the unique identifier for this MachineDeploymentTopology. The value is used with other unique identifiers to create a MachineDeployment&rsquo;s Name (e.g.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].nodeDeletionTimeout</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10m</code></td>
					<td>nodeDeletionTimeout defines how long the controller will attempt to delete the Node that the Machine hosts after the Machine is marked for deletion.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].nodeDrainTimeout</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10m</code></td>
					<td>nodeDrainTimeout is the total amount of time that the controller will spend on draining a node. The default value is 0, meaning that the node can be drained without any time limitations.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].nodeVolumeDetachTimeout</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10m</code></td>
					<td>nodeVolumeDetachTimeout is the total amount of time that the controller will spend on waiting for all volumes to be detached.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].readinessGates</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{conditionType: &lt;condition type&gt;}]</code></td>
					<td>readinessGates specifies additional conditions to include when evaluating Machine Ready condition. This field can be used e.g.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].replicas</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>1</code></td>
					<td>replicas is the number of worker nodes belonging to this set.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].strategy</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{type: RollingUpdate, rollingUpdate: {maxSurge: 1}}</code></td>
					<td>strategy is the deployment strategy to use to replace existing machines with new ones.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machineDeployments[].variables</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{overrides: [{name: vmClass, value: best-effort-large}]}</code></td>
					<td>variables can be used to customize the MachineDeployment through patches.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machinePools</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: mp-1, class: &lt;machine pool class&gt;}]</code></td>
					<td>machinePools is a list of machine pools in the cluster.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machinePools[].class</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;machine pool class&gt;</code></td>
					<td>class is the name of the MachinePoolClass used to create the pool of worker nodes.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machinePools[].failureDomains</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[domain-c9]</code></td>
					<td>failureDomains is the list of failure domains the machine pool will be created in. Must match a key in the FailureDomains map stored on the cluster object.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machinePools[].metadata</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{labels: {pool: mp-1}}</code></td>
					<td>metadata is the metadata applied to the MachinePool. At runtime this metadata is merged with the corresponding metadata from the ClusterClass.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machinePools[].minReadySeconds</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>10</code></td>
					<td>minReadySeconds is the minimum number of seconds for which a newly created machine pool should be ready. Defaults to 0 (machine will be considered available as soon as it is ready)</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machinePools[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>mp-1</code></td>
					<td>name is the unique identifier for this MachinePoolTopology. The value is used with other unique identifiers to create a MachinePool&rsquo;s Name (e.g.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machinePools[].nodeDeletionTimeout</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10m</code></td>
					<td>nodeDeletionTimeout defines how long the controller will attempt to delete the Node that the MachinePool hosts after the MachinePool is marked for deletion.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machinePools[].nodeDrainTimeout</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10m</code></td>
					<td>nodeDrainTimeout is the total amount of time that the controller will spend on draining a node. The default value is 0, meaning that the node can be drained without any time limitations.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machinePools[].nodeVolumeDetachTimeout</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10m</code></td>
					<td>nodeVolumeDetachTimeout is the total amount of time that the controller will spend on waiting for all volumes to be detached.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machinePools[].replicas</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>2</code></td>
					<td>replicas is the number of nodes belonging to this pool.</td>
			</tr>
			<tr>
					<td><code>spec.topology.workers.machinePools[].variables</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{overrides: [{name: vmClass, value: best-effort-large}]}</code></td>
					<td>variables can be used to customize the MachinePool through patches.</td>
			</tr>
	</tbody>
</table>

</details></p>

<p>The ClusterClass&rsquo;s variables (<code>builtin-generic-v3.6.0</code>); <code>vmClass</code> and
<code>storageClass</code> are required:</p>
<table>
	<thead>
			<tr>
					<th>Variable</th>
					<th>What it sets</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>vmClass</code></td>
					<td>The VM class for the nodes.</td>
			</tr>
			<tr>
					<td><code>storageClass</code></td>
					<td>The storage class for node disks.</td>
			</tr>
			<tr>
					<td><code>volumes</code></td>
					<td>Extra node disks: <code>name</code>, <code>capacity</code>, <code>mountPath</code>, <code>storageClass</code>.</td>
			</tr>
			<tr>
					<td><code>node</code></td>
					<td><code>labels</code>, <code>taints</code> and <code>firewall</code> for the nodes.</td>
			</tr>
			<tr>
					<td><code>osConfiguration</code></td>
					<td><code>ntp.servers</code>, <code>trust.additionalTrustedCAs</code>, <code>systemProxy</code> (<code>http</code>, <code>https</code>, <code>noProxy</code>), <code>user</code> (an administrator and its SSH key), <code>sshd</code>, <code>fips</code>, <code>grub</code>, <code>directoryJoin</code>, <code>ubuntuPro</code>, <code>tuned</code>, <code>securityContext</code>.</td>
			</tr>
			<tr>
					<td><code>kubernetes</code></td>
					<td><code>endpointFQDNs</code>, <code>certificateRotation</code> (on by default), and API server, kubelet, controller-manager and etcd settings.</td>
			</tr>
			<tr>
					<td><code>networks</code></td>
					<td>The nodes&rsquo; interfaces: one primary and optional secondary networks.</td>
			</tr>
			<tr>
					<td><code>resourceConfiguration</code></td>
					<td><code>systemReserved</code> CPU and memory for the kubelet.</td>
			</tr>
			<tr>
					<td><code>vsphereOptions</code></td>
					<td><code>persistentVolumes</code>: which storage classes the cluster&rsquo;s PVCs may use.</td>
			</tr>
			<tr>
					<td><code>bootstrapAddons</code></td>
					<td>The CNI, through <code>cniRef</code>.</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (147)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>bootstrapAddons</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{cniRef: {name: &lt;CNI package config&gt;, namespace: &lt;its namespace&gt;}}</code></td>
					<td>BootstrapAddons defines Addons to be installed on Cluster during bootstrapping. Only supported with Kubernetes 1.35 and above.</td>
			</tr>
			<tr>
					<td><code>bootstrapAddons.cniRef</code></td>
					<td>object</td>
					<td>yes</td>
					<td>e.g. <code>{name: &lt;CNI package config&gt;, namespace: &lt;its namespace&gt;}</code></td>
					<td>CNI Addon to instantiate for Cluster. Used to select CNI rather than ClusterBootstrap spec.CNI field. Compatible Addon/AddonRelease must exist.</td>
			</tr>
			<tr>
					<td><code>bootstrapAddons.cniRef.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;CNI package config&gt;</code></td>
					<td>Name of the Addon being referenced.</td>
			</tr>
			<tr>
					<td><code>bootstrapAddons.cniRef.namespace</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>&lt;its namespace&gt;</code></td>
					<td>Namespace of the addon being referenced. If not specified, will use the default public namespace defined by the addon manager.</td>
			</tr>
			<tr>
					<td><code>kubeAPIServerFQDNs</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[api.lab.example.com]</code></td>
					<td>Deprecated: This variable is deprecated. Use kubernetes.endpointFQDNs instead. This variable will be removed in a future release.</td>
			</tr>
			<tr>
					<td><code>kubernetes</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{apiServerConfiguration: {logs: {flushFrequency: 5s, verbosity: 2}, ...}}</code></td>
					<td>Kubernetes configures cluster-wide settings for the Kubernetes cluster, typically applied to the control plane. Supported scopes: cluster, controlPlane, workers</td>
			</tr>
			<tr>
					<td><code>kubernetes.apiServerConfiguration</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{logs: {flushFrequency: 5s, verbosity: 2}, maxMutatingRequestsInFlight: 200}</code></td>
					<td>APIServerConfiguration contains configuration options for the Kubernetes API server.</td>
			</tr>
			<tr>
					<td><code>kubernetes.apiServerConfiguration.logs</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{flushFrequency: 5s, verbosity: 2}</code></td>
					<td>Logging configures the logging options for the API server, including log levels, formats, and output destinations. Refer to the Kubernetes component-base logs options for more information.</td>
			</tr>
			<tr>
					<td><code>kubernetes.apiServerConfiguration.logs.flushFrequency</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>5s</code></td>
					<td>FlushFrequency is the maximum time between log flushes. If specified as a string, it&rsquo;s parsed as a duration (e.g., &ldquo;1s&rdquo;).</td>
			</tr>
			<tr>
					<td><code>kubernetes.apiServerConfiguration.logs.format</code></td>
					<td>string</td>
					<td></td>
					<td><code>text</code>, <code>json</code></td>
					<td>Format specifies the structure of log messages. Supported values are &ldquo;text&rdquo; (default) and &ldquo;json&rdquo;. Corresponds to &ndash;logging-format flag.</td>
			</tr>
			<tr>
					<td><code>kubernetes.apiServerConfiguration.logs.verbosity</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>2</code></td>
					<td>Verbosity is the threshold that determines which log messages are logged. Default is zero which logs only the most important messages.</td>
			</tr>
			<tr>
					<td><code>kubernetes.apiServerConfiguration.maxMutatingRequestsInFlight</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>200</code></td>
					<td>MaxMutatingRequestsInFlight is the maximum number of parallel mutating requests. Every further request has to wait.</td>
			</tr>
			<tr>
					<td><code>kubernetes.apiServerConfiguration.maxRequestsInFlight</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>400</code></td>
					<td>MaxRequestsInFlight is the maximum number of parallel non-long-running requests. Every further request has to wait.</td>
			</tr>
			<tr>
					<td><code>kubernetes.apiServerConfiguration.profiling</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>Profiling enables profiling via web interface host:port/debug/pprof/ Default: false</td>
			</tr>
			<tr>
					<td><code>kubernetes.apiServerConfiguration.requestTimeout</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>60s</code></td>
					<td>RequestTimeout is the duration after which all non-long-running requests will be timed out. Corresponds to the &ndash;request-timeout flag.</td>
			</tr>
			<tr>
					<td><code>kubernetes.certificateRotation</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{enabled: true, renewalDaysBeforeExpiry: 90}</code></td>
					<td>CertificateRotation configures options for the automatic rotation of control plane certificates which have a default validity of 12 months.</td>
			</tr>
			<tr>
					<td><code>kubernetes.certificateRotation.enabled</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>true</code></td>
					<td>Enabled controls enablement of auto certificate rotation</td>
			</tr>
			<tr>
					<td><code>kubernetes.certificateRotation.renewalDaysBeforeExpiry</code></td>
					<td>integer</td>
					<td></td>
					<td>default <code>90</code></td>
					<td>RenewalDaysBeforeExpiry states the number of days before certificate expiry to initiate the renewal of certificates.</td>
			</tr>
			<tr>
					<td><code>kubernetes.endpointFQDNs</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[api.lab.example.com]</code></td>
					<td>EndpointFQDNs Configure FQDN aliases for the control plane endpoint for example to allow users to connect to the cluster using <a href="https://k8s.prod.example.com/">https://k8s.prod.example.com/</a></td>
			</tr>
			<tr>
					<td><code>kubernetes.etcdConfiguration</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{maximumDBSizeGiB: 8}</code></td>
					<td>EtcdConfiguration contains configuration options for the etcd database used by Kubernetes. These settings control etcd behavior including database size limits and performance tuning.</td>
			</tr>
			<tr>
					<td><code>kubernetes.etcdConfiguration.maximumDBSizeGiB</code></td>
					<td>integer</td>
					<td>yes</td>
					<td>e.g. <code>8</code></td>
					<td>MaximumDBSizeGiB specifies the maximum size of the etcd database in GiB. This value is used to set &ndash;quota-backend-bytes for etcd.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeControllerManagerConfiguration</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{terminatedPodGCThreshold: 1000}</code></td>
					<td>KubeControllerManagerConfiguration contains configuration options for the kube-controller-manager. Supported scopes: cluster, controlPlane</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeControllerManagerConfiguration.terminatedPodGCThreshold</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>1000</code></td>
					<td>TerminatedPodGCThreshold is the number of terminated pods that can exist before the terminated pod garbage collector starts deleting terminated pods.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{allowedUnsafeSysctls: [net.core.somaxconn], eventBurst: 100}</code></td>
					<td>KubeletConfiguration contains configuration options for the kubelet running on worker nodes.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.allowedUnsafeSysctls</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[net.core.somaxconn]</code></td>
					<td>AllowedUnsafeSysctls is a comma separated allowlist of unsafe sysctls or sysctl patterns (ending in <code>*</code>). All safe sysctls are enabled by default.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.containerLogMaxFiles</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>5</code></td>
					<td>ContainerLogMaxFiles is the maximum number of container log files that can be present for a container. Default: 5</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.containerLogMaxSizeMiB</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>10</code></td>
					<td>ContainerLogMaxSize defines the maximum size of the container log file before it is rotated in MiB.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.eventBurst</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>100</code></td>
					<td>EventBurst is the maximum size of a burst of event creations, temporarily allows event creations to burst to this number, while still not exceeding eventRecordQPS.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.eventRecordQPS</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>50</code></td>
					<td>EventRecordQPS is the maximum event creations per second. If 0, there is no limit enforced. Corresponds to &ndash;event-qps kubelet flag.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.healthzBindAddress</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>127.0.0.1</code></td>
					<td>HealthzBindAddress is the IP address for the healthz server to serve on. Default: &ldquo;127.0.0.1&rdquo;</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.imageGCHighThresholdPercent</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>85</code></td>
					<td>ImageGCHighThresholdPercent is the percent of disk usage after which image garbage collection is always run. The percent is calculated as this field value out of 100.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.imageGCLowThresholdPercent</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>80</code></td>
					<td>ImageGCLowThresholdPercent is the percent of disk usage before which image garbage collection is never run. Lowest disk usage to garbage collect to.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.imageMaximumGCAge</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>168h</code></td>
					<td>ImageMaximumGCAge is the maximum age an image can be unused before it is garbage collected.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.imageMinimumGCAge</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>2m</code></td>
					<td>ImageMinimumGCAge is the minimum age for an unused image before it is garbage collected. Default: &ldquo;2m&rdquo;</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.imagePullCredentialsVerificationPolicy</code></td>
					<td>string</td>
					<td></td>
					<td><code>NeverVerify</code>, <code>NeverVerifyPreloadedImages</code>, <code>NeverVerifyAllowlistedImages</code>, <code>AlwaysVerify</code></td>
					<td>ImagePullCredentialsVerificationPolicy determines how credentials should be verified when pod requests an image that is already present on the node.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.logging</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{flushFrequency: 5s, verbosity: 2}</code></td>
					<td>Logging specifies the logging configuration options for the kubelet. This controls log levels, formats, and output destinations for kubelet logs.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.logging.flushFrequency</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>5s</code></td>
					<td>FlushFrequency is the maximum time between log flushes. If specified as a string, it&rsquo;s parsed as a duration (e.g., &ldquo;1s&rdquo;).</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.logging.format</code></td>
					<td>string</td>
					<td></td>
					<td><code>text</code>, <code>json</code></td>
					<td>Format specifies the structure of log messages. Supported values are &ldquo;text&rdquo; (default) and &ldquo;json&rdquo;. Corresponds to &ndash;logging-format flag.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.logging.verbosity</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>2</code></td>
					<td>Verbosity is the threshold that determines which log messages are logged. Default is zero which logs only the most important messages.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.maxParallelImagePulls</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>5</code></td>
					<td>MaxParallelImagePulls sets the maximum number of image pulls in parallel. This field is only used when SerializeImagePulls is false.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.maxPods</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>110</code></td>
					<td>MaxPods is the number of pods that can run on this Kubelet. Default: 110 NOTE: By default, the maximum allowed value is 250.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.podPidsLimit</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>4096</code></td>
					<td>PodPidsLimit is the maximum number of PIDs in any pod. Use Kubelet default (-1) when omitted. Default: nil</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.preloadedImagesVerificationAllowlist</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[registry.example.local/*]</code></td>
					<td>PreloadedImagesVerificationAllowlist specifies a list of images that are exempted from credential reverification for the &ldquo;NeverVerifyAllowlistedImages&rdquo; <code>imagePullCredentialsVerificationPolicy</code>.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.registryBurst</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>10</code></td>
					<td>RegistryBurst is the maximum size of bursty pulls, temporarily allows pulls to burst to this number, while still not exceeding registryPullQPS.</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.registryPullQPS</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>5</code></td>
					<td>RegistryPullQPS is the limit of registry pulls per second. Set to 0 for no limit. Default: 5</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.serializeImagePulls</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>SerializeImagePulls when enabled, tells the Kubelet to pull images one at a time. Default: true</td>
			</tr>
			<tr>
					<td><code>kubernetes.kubeletConfiguration.streamingConnectionIdleTimeout</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>4h</code></td>
					<td>StreamingConnectionIdleTimeout is the maximum time a streaming connection can be idle before the connection is automatically closed.</td>
			</tr>
			<tr>
					<td><code>kubernetes.security</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{podSecurityStandard: {auditVersion: latest, enforceVersion: latest}, ...}</code></td>
					<td>Security configures Kubernetes specific security settings.</td>
			</tr>
			<tr>
					<td><code>kubernetes.security.podSecurityStandard</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{auditVersion: latest, enforceVersion: latest}</code></td>
					<td>PodSecurityStandard configures the PodSecurityStandard settings for the cluster.</td>
			</tr>
			<tr>
					<td><code>kubernetes.security.podSecurityStandard.audit</code></td>
					<td>string</td>
					<td></td>
					<td>``, <code>privileged</code>, <code>baseline</code>, <code>restricted</code></td>
					<td>Audit sets the level for the audit PodSecurityConfiguration mode. Policy violations trigger an audit annotation, but are otherwise allowed One of &ldquo;&rdquo;, privileged, baseline, restricted.</td>
			</tr>
			<tr>
					<td><code>kubernetes.security.podSecurityStandard.auditVersion</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>latest</code></td>
					<td>AuditVersion can be used to pin the policy to the version that shipped with a given Kubernetes minor version (e.g. v1.31) when in audit mode.</td>
			</tr>
			<tr>
					<td><code>kubernetes.security.podSecurityStandard.deactivated</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Deactivated disables the patches for Pod Security Standard via AdmissionConfiguration.</td>
			</tr>
			<tr>
					<td><code>kubernetes.security.podSecurityStandard.enforce</code></td>
					<td>string</td>
					<td></td>
					<td>``, <code>privileged</code>, <code>baseline</code>, <code>restricted</code></td>
					<td>Enforce sets the level for the enforce PodSecurityConfiguration mode. Policy violations cause the pod to be rejected.</td>
			</tr>
			<tr>
					<td><code>kubernetes.security.podSecurityStandard.enforceVersion</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>latest</code></td>
					<td>EnforceVersion can be used to pin the policy to the version that shipped with a given Kubernetes minor version (e.g.</td>
			</tr>
			<tr>
					<td><code>kubernetes.security.podSecurityStandard.exemptions</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{namespaces: [monitoring]}</code></td>
					<td>Exemptions can be statically configured based on (requesting) user, RuntimeClass, or namespace. A request meeting exemption criteria is ignored by the admission plugin.</td>
			</tr>
			<tr>
					<td><code>kubernetes.security.podSecurityStandard.warn</code></td>
					<td>string</td>
					<td></td>
					<td>``, <code>privileged</code>, <code>baseline</code>, <code>restricted</code></td>
					<td>Warn sets the level for the warn PodSecurityConfiguration mode. Policy violations trigger a user-facing warning, but are otherwise allowed.</td>
			</tr>
			<tr>
					<td><code>kubernetes.security.podSecurityStandard.warnVersion</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>latest</code></td>
					<td>WarnVersion can be used to pin the policy to the version that shipped with a given Kubernetes minor version (e.g. v1.31) when in warn mode.</td>
			</tr>
			<tr>
					<td><code>kubernetes.security.resourceQuotaConfiguration</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{enabled: false}</code></td>
					<td>ResourceQuotaConfiguration configures the ResourceQuota admission control settings for the cluster.</td>
			</tr>
			<tr>
					<td><code>kubernetes.security.resourceQuotaConfiguration.enabled</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Enabled enables the patches for ResourceQuotaConfiguration via AdmissionConfiguration.</td>
			</tr>
			<tr>
					<td><code>networks</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{interfaces: {primary: {network: {apiVersion: crd.nsx.vmware.com/v1alpha1, ...}}}}</code></td>
					<td>Networks defines the network configuration for the cluster</td>
			</tr>
			<tr>
					<td><code>networks.interfaces</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{primary: {network: {apiVersion: crd.nsx.vmware.com/v1alpha1, kind: SubnetSet, ...}}}</code></td>
					<td>Interfaces describes one primary (eth0) and zero or more secondary interfaces attached to Node virtual machine.</td>
			</tr>
			<tr>
					<td><code>networks.interfaces.primary</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{network: {apiVersion: crd.nsx.vmware.com/v1alpha1, kind: SubnetSet, ...}}</code></td>
					<td>Primary is the primary network interface which is used to connect the Kubernetes primary network for Load balancer, Service discovery, Pod traffic and management traffic etc.</td>
			</tr>
			<tr>
					<td><code>networks.interfaces.primary.mtu</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>1500</code></td>
					<td>MTU is the Maximum Transmission Unit size in bytes.</td>
			</tr>
			<tr>
					<td><code>networks.interfaces.primary.network</code></td>
					<td>object</td>
					<td>yes</td>
					<td>e.g. <code>{apiVersion: crd.nsx.vmware.com/v1alpha1, kind: SubnetSet, name: &lt;subnet set&gt;}</code></td>
					<td>Network is the name of the network resource to which this interface is connected.</td>
			</tr>
			<tr>
					<td><code>networks.interfaces.primary.routes</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{to: 172.16.0.0/16, via: 10.244.0.1}]</code></td>
					<td>Routes is a list of optional, static routes.</td>
			</tr>
			<tr>
					<td><code>networks.interfaces.secondary</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: eth1, network: {apiVersion: crd.nsx.vmware.com/v1alpha1, kind: Subnet, ...}}]</code></td>
					<td>Secondary network is supported with network provider NSX-VPC and vsphere-network.</td>
			</tr>
			<tr>
					<td><code>networks.interfaces.secondary[].mtu</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>1500</code></td>
					<td>MTU is the Maximum Transmission Unit size in bytes.</td>
			</tr>
			<tr>
					<td><code>networks.interfaces.secondary[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>eth1</code></td>
					<td>Name describes the unique name of this network interface, used to distinguish it from other network interfaces attached to node Virtual Machine.</td>
			</tr>
			<tr>
					<td><code>networks.interfaces.secondary[].network</code></td>
					<td>object</td>
					<td>yes</td>
					<td>e.g. <code>{apiVersion: crd.nsx.vmware.com/v1alpha1, kind: Subnet, name: storage-net}</code></td>
					<td>Network is the name of the network resource to which this interface is connected.</td>
			</tr>
			<tr>
					<td><code>networks.interfaces.secondary[].routes</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{to: 10.50.0.0/16, via: 10.250.0.1}]</code></td>
					<td>Routes is a list of optional, static routes.</td>
			</tr>
			<tr>
					<td><code>node</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{firewall: {inboundRules: [{fromPort: 30000, protocol: TCP}]}, labels: {workload: web}}</code></td>
					<td>Node configures Kubernetes node specific settings. Supported scopes: cluster, controlPlane, workers</td>
			</tr>
			<tr>
					<td><code>node.firewall</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{inboundRules: [{fromPort: 30000, protocol: TCP}]}</code></td>
					<td>Firewall specifies the firewall configuration that should be created on the node to allow specific kinds of traffic.</td>
			</tr>
			<tr>
					<td><code>node.firewall.inboundRules</code></td>
					<td>array of object</td>
					<td>yes</td>
					<td>e.g. <code>[{fromPort: 30000, protocol: TCP}]</code></td>
					<td>InboundRules is a list of firewall rules that will be configured on each node to allow or deny specific kinds of traffic.</td>
			</tr>
			<tr>
					<td><code>node.firewall.inboundRules[].fromPort</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>30000</code></td>
					<td>FromPort is the low end (inclusive) of the port range that this rule applies to.</td>
			</tr>
			<tr>
					<td><code>node.firewall.inboundRules[].protocol</code></td>
					<td>int or string</td>
					<td>yes</td>
					<td>e.g. <code>TCP</code></td>
					<td>Protocol is the type of traffic that this rule applies to. Allowed protocols include &ldquo;tcp&rdquo;, &ldquo;udp&rdquo;, &ldquo;icmp&rdquo;, or an any valid IANA protocol number.</td>
			</tr>
			<tr>
					<td><code>node.firewall.inboundRules[].source</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>10.0.0.0/8</code></td>
					<td>Source is the CIDR range of the originating traffic that this rule applies to. If unset, the rule will apply to any source network.</td>
			</tr>
			<tr>
					<td><code>node.firewall.inboundRules[].toPort</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>32767</code></td>
					<td>ToPort is the high end (inclusive) of the port range that this rule applies to.</td>
			</tr>
			<tr>
					<td><code>node.labels</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{workload: web}</code></td>
					<td>Labels is a list of user defined name-value pairs</td>
			</tr>
			<tr>
					<td><code>node.taints</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: dedicated, value: gpu}]</code></td>
					<td>Taints specifies the taints the Node API object should be registered with. If this field is unset, i.e. nil, it will be defaulted with a control-plane taint for control-plane nodes.</td>
			</tr>
			<tr>
					<td><code>node.taints[].effect</code></td>
					<td>string</td>
					<td>yes</td>
					<td><code>NoSchedule</code>, <code>PreferNoSchedule</code>, <code>NoExecute</code></td>
					<td>Effect of the taint on pods that do not tolerate the taint. Valid effects are NoSchedule, PreferNoSchedule and NoExecute.</td>
			</tr>
			<tr>
					<td><code>node.taints[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>dedicated</code></td>
					<td>Key is the taint key to be applied to a node.</td>
			</tr>
			<tr>
					<td><code>node.taints[].value</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>gpu</code></td>
					<td>Value is the taint value corresponding to the taint key.</td>
			</tr>
			<tr>
					<td><code>osConfiguration</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{ntp: {servers: [172.30.0.34]}, ...}</code></td>
					<td>OSConfiguration configures the system settings of nodes that are independent of Kubernetes. Supported scopes: cluster, controlPlane, workers</td>
			</tr>
			<tr>
					<td><code>osConfiguration.directoryJoin</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{credentialSecretRef: &lt;Secret with the join account&gt;, domain: example.local}</code></td>
					<td>DirectoryJoin configures the node to join a Windows Active Directory. Only supported on Windows at present.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.directoryJoin.credentialSecretRef</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;Secret with the join account&gt;</code></td>
					<td>CredentialSecretRef is the name of the secret containing Active Directory join credentials.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.directoryJoin.domain</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>example.local</code></td>
					<td>Domain is the FQDN of the Active Directory Kerberos domain to join.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.directoryJoin.gmsaControlSecurityGroupDN</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>CN=gmsa-k8s,OU=Groups,DC=example,DC=local</code></td>
					<td>GMSAControlSecurityGroupDN is an optional Windows Active Directory security group that has permissions to access the password of the Group Managed Service Accounts.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.directoryJoin.organizationalUnitDN</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>OU=K8s,DC=example,DC=local</code></td>
					<td>OrganizationalUnitDN is an optional organizational unit where the node will be added to in Active Directory. The value will be validated according to <a href="https://tools.ietf.org/html/rfc4514">https://tools.ietf.org/html/rfc4514</a></td>
			</tr>
			<tr>
					<td><code>osConfiguration.fips</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{enabled: false}</code></td>
					<td>FIPS configures FIPS related settings for the Kubernetes cluster to run in FIPS mode. Supported scopes: cluster</td>
			</tr>
			<tr>
					<td><code>osConfiguration.fips.enabled</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Enable specifies whether FIPS settings are enabled and enforced on the node</td>
			</tr>
			<tr>
					<td><code>osConfiguration.grub</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{password: {secretRef: {name: &lt;Secret&gt;, key: password}, user: root}}</code></td>
					<td>GRUB configures GRUB Boot Loader.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.grub.password</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{secretRef: {name: &lt;Secret&gt;, key: password}, user: root}</code></td>
					<td>Password configures the password protection for GRUB Boot Loader (Only applicable on Linux).</td>
			</tr>
			<tr>
					<td><code>osConfiguration.grub.password.enabled</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>false</code></td>
					<td>Enabled defines if the GRUB Boot Loader must be protected with a password</td>
			</tr>
			<tr>
					<td><code>osConfiguration.grub.password.secretRef</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{name: &lt;Secret&gt;, key: password}</code></td>
					<td>SecretRef is the name of the secret containing the password to protect GRUB Key is the data.key field within the secret containing the password value.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.grub.password.user</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>root</code></td>
					<td>User specifies the username to use for GRUB password protection.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.ntp</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{servers: [172.30.0.34]}</code></td>
					<td>NTP sets the time servers that will be used by nodes in the cluster. By default, NTP servers are inherited from vCenter.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.ntp.servers</code></td>
					<td>array of string</td>
					<td>yes</td>
					<td>e.g. <code>[172.30.0.34]</code></td>
					<td>NTP sets the time servers that will be used by nodes in this cluster. By default, NTP servers are inherited from vCenter.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.securityContext</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{appArmor: {profiles: [{name: &lt;AppArmor profile&gt;}]}}</code></td>
					<td>SecurityContext holds security configurations that will be applied to node.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.securityContext.appArmor</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{profiles: [{name: &lt;AppArmor profile&gt;}]}</code></td>
					<td>AppArmor configures the appArmor profiles of the node. Supported scopes: cluster, controlPlane, workers Only supported on Ubuntu and Photon nodes.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.securityContext.appArmor.profiles</code></td>
					<td>array of object</td>
					<td>yes</td>
					<td>e.g. <code>[{name: &lt;AppArmor profile&gt;}]</code></td>
					<td>Profiles is a list of appArmor profiles to be added to the node.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.sshd</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{banner: Authorised use only}</code></td>
					<td>SSHD configures the sshd config of the node.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.sshd.banner</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>Authorised use only</code></td>
					<td>Banner specifies the login message used for sending a legal warning message before authentication</td>
			</tr>
			<tr>
					<td><code>osConfiguration.systemProxy</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{http: http://proxy.example.local:3128, https: http://proxy.example.local:3128}</code></td>
					<td>SystemProxy configures parameters that reference a proxy server for outbound cluster connections.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.systemProxy.http</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>http://proxy.example.local:3128</code></td>
					<td>HTTP is the proxy server to be used for all http connections. This should be a hostname or dotted numerical IP address.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.systemProxy.https</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>http://proxy.example.local:3128</code></td>
					<td>HTTPS configures the proxy server to be used for all https connections. This should be a hostname or dotted numerical IP address.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.systemProxy.noProxy</code></td>
					<td>array of string</td>
					<td>yes</td>
					<td>e.g. <code>[.example.local, 10.0.0.0/8]</code></td>
					<td>NoProxy configures the list of hostnames and CIDR ranges that should be reached without the configured proxy servers.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.trust</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{additionalTrustedCAs: [{caCert: {secretRef: {name: corp-ca}}}]}</code></td>
					<td>Trust configures system-wide certificate trust for nodes</td>
			</tr>
			<tr>
					<td><code>osConfiguration.trust.additionalTrustedCAs</code></td>
					<td>array of object</td>
					<td>yes</td>
					<td>e.g. <code>[{caCert: {secretRef: {name: corp-ca}}}]</code></td>
					<td>AdditionalTrustedCAs is a list of additional CAs to be added to the system trust store of nodes.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.trust.additionalTrustedCAs[].caCert</code></td>
					<td>object</td>
					<td>yes</td>
					<td>e.g. <code>{secretRef: {name: corp-ca, key: ca.crt}}</code></td>
					<td>SecretContent configures a reference to or content of secret data.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.tuned</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{active: [&lt;tuned profile&gt;], profiles: {&lt;profile name&gt;: &lt;TunedProfile reference&gt;}}</code></td>
					<td>TuneD injects TuneD profiles and activate specified profile on Linux nodes. Only supported on Linux.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.tuned.active</code></td>
					<td>array of string</td>
					<td>yes</td>
					<td>e.g. <code>[&lt;tuned profile&gt;]</code></td>
					<td>Active is a list of tuned profile name will be activated on node.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.tuned.profiles</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{&lt;profile name&gt;: &lt;TunedProfile reference&gt;}</code></td>
					<td>Profiles is a map of tuned profiles will be injected on node. Key is the desired tuned profile name, value is the TunedProfile CR reference which contains the profile content.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.ubuntuPro</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{services: [usg], settings: [{key: &lt;setting&gt;, value: &lt;value&gt;}]}</code></td>
					<td>UbuntuPro configures the Ubuntu Pro subscription of the node. Only supported on Ubuntu.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.ubuntuPro.services</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[usg]</code></td>
					<td>Services specifies the Ubuntu Pro services to be enabled.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.ubuntuPro.settings</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{key: &lt;setting&gt;, value: &lt;value&gt;}]</code></td>
					<td>Settings specifies the Ubuntu Pro client (ubuntu-advantage-tools) settings to be configured.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.ubuntuPro.settings[].key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;setting&gt;</code></td>
					<td></td>
			</tr>
			<tr>
					<td><code>osConfiguration.ubuntuPro.settings[].value</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;value&gt;</code></td>
					<td></td>
			</tr>
			<tr>
					<td><code>osConfiguration.ubuntuPro.tokenSecretRef</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;Secret with the Pro token&gt;</code></td>
					<td>TokenSecretRef is the name of the secret containing a valid Ubuntu Pro Subscription token. The secret must have a key token with the content of a valid token.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.user</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{passwordSecret: {key: password, name: &lt;Secret&gt;}, ...}</code></td>
					<td>User is an administrative user that will be created on all nodes. If not set, this is defaulted to &ldquo;vmware-system-user&rdquo;.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.user.password</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{renewalDaysBeforeExpiry: 30}</code></td>
					<td>Password configures the password policy such as password max age and renewal settings.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.user.password.renewalDaysBeforeExpiry</code></td>
					<td>integer</td>
					<td></td>
					<td>e.g. <code>30</code></td>
					<td>RenewalDaysBeforeExpiry configures the days to renew the password before it gets expired.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.user.passwordSecret</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{key: password, name: &lt;Secret&gt;}</code></td>
					<td>Key is the data.key field within the secret containing the password value. If not specified, the secret will be automatically generated as <!-- raw HTML omitted -->-ssh-password.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.user.passwordSecret.key</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>password</code></td>
					<td>Key is the data.key field within the secret containing the password value. For Linux, this must be the hashed value that should be inserted into /etc/shadow.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.user.passwordSecret.name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>&lt;Secret&gt;</code></td>
					<td>Name is the name of the secret containing the password for the administrative account.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.user.requirePasswordOnSudo</code></td>
					<td>boolean</td>
					<td></td>
					<td>e.g. <code>true</code></td>
					<td>RequirePasswordOnSudo configures whether password re-authentication is required on sudo.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.user.sshAuthorizedKey</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>ssh-ed25519 AAAA... ops@admin</code></td>
					<td>The string of the SSH public key that is to be used for the administrative account. The public key must be of any FIPS-140 approved algorithm.</td>
			</tr>
			<tr>
					<td><code>osConfiguration.user.user</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>vmware-system-user</code></td>
					<td>Name is the name of the user to be created. By default, this is vmware-system-user.</td>
			</tr>
			<tr>
					<td><code>resourceConfiguration</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{systemReserved: {cpu: 500m, memory: 1Gi}}</code></td>
					<td>ResourceConfiguration configures kubelet resource options. Currently, only CPU and memory reservations are supported.</td>
			</tr>
			<tr>
					<td><code>resourceConfiguration.systemReserved</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{cpu: 500m, memory: 1Gi}</code></td>
					<td>SystemReserved defines the system reserved CPU and memory reservations.</td>
			</tr>
			<tr>
					<td><code>resourceConfiguration.systemReserved.automatic</code></td>
					<td>boolean</td>
					<td></td>
					<td>default <code>true</code></td>
					<td>Automatic controls the automatic calculation of system reserved resources.</td>
			</tr>
			<tr>
					<td><code>resourceConfiguration.systemReserved.cpu</code></td>
					<td>int or string</td>
					<td></td>
					<td>e.g. <code>500m</code></td>
					<td>CPU describes the number of CPU cores reserved for system processes.</td>
			</tr>
			<tr>
					<td><code>resourceConfiguration.systemReserved.memory</code></td>
					<td>int or string</td>
					<td></td>
					<td>e.g. <code>1Gi</code></td>
					<td>Memory describes the memory resources reserved for system processes.</td>
			</tr>
			<tr>
					<td><code>storageClass</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>vsan-default-storage-policy</code></td>
					<td>StorageClass sets the StorageClass that will be used to create node root volumes.</td>
			</tr>
			<tr>
					<td><code>vmClass</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>best-effort-small</code></td>
					<td>VMClass sets the VMClass that will be used to create nodes. Supported scopes: cluster, controlPlane, workers</td>
			</tr>
			<tr>
					<td><code>volumes</code></td>
					<td>array of object</td>
					<td></td>
					<td>e.g. <code>[{name: containerd, capacity: 50Gi}]</code></td>
					<td>Volumes configures additional disks to be attached to node virtual machines. Supported scopes: cluster, controlPlane, workers</td>
			</tr>
			<tr>
					<td><code>volumes[].capacity</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>50Gi</code></td>
					<td>Capacity defines the storage capacity of the volume.</td>
			</tr>
			<tr>
					<td><code>volumes[].mountPath</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>/var/lib/containerd</code></td>
					<td>MountPath defines the mount path for the volume.</td>
			</tr>
			<tr>
					<td><code>volumes[].name</code></td>
					<td>string</td>
					<td>yes</td>
					<td>e.g. <code>containerd</code></td>
					<td>Name defines the name of the volume.</td>
			</tr>
			<tr>
					<td><code>volumes[].storageClass</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>vsan-default-storage-policy</code></td>
					<td>StorageClass defines the Storage class to use for the volume.</td>
			</tr>
			<tr>
					<td><code>vsphereOptions</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{persistentVolumes: {availableStorageClasses: [vsan-default-storage-policy], ...}}</code></td>
					<td>VSphereOptions configures vSphere specific options related to nodes Supported scopes: cluster, controlPlane, workers</td>
			</tr>
			<tr>
					<td><code>vsphereOptions.persistentVolumes</code></td>
					<td>object</td>
					<td></td>
					<td>e.g. <code>{availableStorageClasses: [vsan-default-storage-policy], ...}</code></td>
					<td>PersistentVolumes configures what is available for PVCs to be used in the cluster.</td>
			</tr>
			<tr>
					<td><code>vsphereOptions.persistentVolumes.availableStorageClasses</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[vsan-default-storage-policy]</code></td>
					<td>AvailableStorageClasses lists the storage classes that can be used in the cluster.</td>
			</tr>
			<tr>
					<td><code>vsphereOptions.persistentVolumes.availableVolumeSnapshotClasses</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[volumesnapshotclass-delete]</code></td>
					<td>AvailableVolumeSnapshotClasses lists the volume snapshot classes that can be used in the cluster.</td>
			</tr>
			<tr>
					<td><code>vsphereOptions.persistentVolumes.customizableStorageClassAnnotations</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[&lt;annotation&gt;]</code></td>
					<td>CustomizableStorageClassAnnotations is a list of annotation keys set on the storage classes within the cluster which can be customized by the user.</td>
			</tr>
			<tr>
					<td><code>vsphereOptions.persistentVolumes.customizableStorageClassLabels</code></td>
					<td>array of string</td>
					<td></td>
					<td>e.g. <code>[&lt;label&gt;]</code></td>
					<td>CustomizableStorageClassLabels is a list of label keys set on the storage classes within the cluster which can be customized by the user.</td>
			</tr>
			<tr>
					<td><code>vsphereOptions.persistentVolumes.defaultStorageClass</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>vsan-default-storage-policy</code></td>
					<td>DefaultStorageClass sets the default storage class inside the cluster.</td>
			</tr>
			<tr>
					<td><code>vsphereOptions.persistentVolumes.defaultVolumeSnapshotClass</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>volumesnapshotclass-delete</code></td>
					<td>DefaultVolumeSnapshotClass sets the default volume snapshot class inside the cluster.</td>
			</tr>
	</tbody>
</table>

</details></p>

<p>Recipe, one control plane node and one worker, as we deployed it (ready in
four minutes):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">k8s</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">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">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.namespace.id}</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">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">cluster.x-k8s.io/v1beta1</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">Cluster</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">dev-01}</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">clusterNetwork</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">pods</span><span class="p">:</span><span class="w"> </span>{<span class="nt">cidrBlocks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="m">192.168.156.0</span><span class="l">/20]}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">services</span><span class="p">:</span><span class="w"> </span>{<span class="nt">cidrBlocks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="m">10.96.0.0</span><span class="l">/12]}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">serviceDomain</span><span class="p">:</span><span class="w"> </span><span class="l">cluster.local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">topology</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="l">builtin-generic-v3.6.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">classNamespace</span><span class="p">:</span><span class="w"> </span><span class="l">vmware-system-vks-public</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="l">v1.35.5+vmware.1-vkr.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">controlPlane</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">workers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">machineDeployments</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span>- <span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="l">node-pool</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">np-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">variables</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">vmClass</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">best-effort-small</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">storageClass</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">vsan-default-storage-policy</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">osConfiguration</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">ntp</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="nt">servers</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="m">172.30.0.34</span><span class="p">]</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">conditions</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">Ready</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">status</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;True&#34;</span><span class="w">
</span></span></span></code></pre></div><p>The same cluster in <code>v1beta2</code>, the version the Supervisor recommends; the
class becomes a reference with its namespace:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">        </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">cluster.x-k8s.io/v1beta2</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">Cluster</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">dev-01}</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">clusterNetwork</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">pods</span><span class="p">:</span><span class="w"> </span>{<span class="nt">cidrBlocks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="m">192.168.156.0</span><span class="l">/20]}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">services</span><span class="p">:</span><span class="w"> </span>{<span class="nt">cidrBlocks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="m">10.96.0.0</span><span class="l">/12]}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">serviceDomain</span><span class="p">:</span><span class="w"> </span><span class="l">cluster.local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">topology</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">classRef</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">builtin-generic-v3.6.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">vmware-system-vks-public</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="l">v1.35.5+vmware.1-vkr.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">controlPlane</span><span class="p">:</span><span class="w"> </span>{<span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">workers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">machineDeployments</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span>- {<span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="nt">node-pool, name</span><span class="p">:</span><span class="w"> </span><span class="nt">np-1, replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">variables</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">vmClass, value</span><span class="p">:</span><span class="w"> </span><span class="l">best-effort-small}</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">storageClass, value</span><span class="p">:</span><span class="w"> </span><span class="l">vsan-default-storage-policy}</span><span class="w">
</span></span></span></code></pre></div><p><strong>Status worth reading:</strong> <code>status.phase</code>, <code>status.conditions</code>; the kubeconfig
is in the namespace as the Secret <code>&lt;cluster&gt;-kubeconfig</code> (ours:
<code>dev-01-kubeconfig</code>).</p>
<p><strong>Gotchas</strong></p>
<ul>
<li>Every namespace gets copies of a few ClusterClasses, on f06
<code>builtin-generic-v3.1.0</code> to <code>v3.3.0</code>; Broadcom&rsquo;s ClusterClass matrix marks
<code>v3.3.0</code> deprecated for VKS 3.6 and 3.7, and Broadcom&rsquo;s own 9.1 sample still
uses it. The newer classes live only in <code>vmware-system-vks-public</code>: name
that namespace (<code>classNamespace</code>, or <code>classRef.namespace</code> in <code>v1beta2</code>) to
use them.</li>
<li>The palette&rsquo;s <code>v1beta1</code> works, with a deprecation warning.</li>
<li>Nodes run Photon OS unless the cluster carries the annotation
<code>run.tanzu.vmware.com/resolve-os-image: os-name=ubuntu</code>.</li>
<li><code>topology.version</code> must be a release the Supervisor lists as ready and
compatible; on f06 those were <code>v1.32.x</code> to <code>v1.35.5</code>.</li>
<li>VKS needs the VPC&rsquo;s load balancer for the cluster&rsquo;s API endpoint, so the
namespace must use a VPC that has one.</li>
</ul>
<h2 id="utilpasswordentry">Util.PasswordEntry</h2>
<p><code>type: Util.PasswordEntry</code>. Not in the palette, but one of the five types: it
generates a password, or takes one you give it, and hashes it at request
time.</p>
<table>
	<thead>
			<tr>
					<th>Property</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>length</code></td>
					<td>Generate a password of this length. Default 14.</td>
			</tr>
			<tr>
					<td><code>password</code></td>
					<td>A password to use instead, when <code>length</code> is not set.</td>
			</tr>
			<tr>
					<td><code>generatedPassword</code></td>
					<td>Computed: the generated password.</td>
			</tr>
			<tr>
					<td><code>sha512crypt</code></td>
					<td>Computed: the password&rsquo;s SHA-512 crypt hash (<code>$6$...</code>).</td>
			</tr>
	</tbody>
</table>


<p><details >
  <summary markdown="span">Every field the platform accepts (3)</summary>
  <table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Req.</th>
					<th>Values</th>
					<th>Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>count</code></td>
					<td>integer</td>
					<td></td>
					<td>default <code>1</code></td>
					<td>The number of resource instances to be created.</td>
			</tr>
			<tr>
					<td><code>length</code></td>
					<td>integer</td>
					<td></td>
					<td>default <code>14</code></td>
					<td>Length of the password to be auto generated</td>
			</tr>
			<tr>
					<td><code>password</code></td>
					<td>string</td>
					<td></td>
					<td>e.g. <code>${input.adminPassword}</code></td>
					<td>Password value received as an input when length is not specified</td>
			</tr>
	</tbody>
</table>

</details></p>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pw</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">Util.PasswordEntry</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">length</span><span class="p">:</span><span class="w"> </span><span class="m">20</span><span class="w">
</span></span></span></code></pre></div><p>VCF Automation stores both computed values as encrypted secrets
(<code>((secret:v1:...))</code>), so the deployment doesn&rsquo;t show them. Where the hash is
used decides how to write it:</p>
<ul>
<li>
<p>In a raw cloud-config held in a Secret, it is a string:
<code>hashed_passwd: ${resource.pw.sha512crypt}</code>.</p>
</li>
<li>
<p>In a VM&rsquo;s inline <code>cloudConfig</code> it must be a Secret reference, so put it in
a Secret first:</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">webPw</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">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">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.ns.id}</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">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</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">Secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">web-pw}</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">Opaque</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">stringData</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ops-passwd</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.pw.sha512crypt}</span><span class="w">
</span></span></span></code></pre></div></li>
</ul>
<h2 id="complete-tested-blueprints">Complete, tested blueprints</h2>
<p>The five blueprints we deployed to test this guide, as they ran:</p>
<table>
	<thead>
			<tr>
					<th>File</th>
					<th>What it builds</th>
					<th>Result</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>test1-vpc.yaml</code></td>
					<td>A VPC, its attachment, an external IP allocation, a group, a gateway firewall policy referencing the group, a namespace in the new VPC, a generated password, two counted Secrets holding its hash</td>
					<td>Created in 2 min 15 s; every VPC object <code>Realized</code></td>
			</tr>
			<tr>
					<td><code>test1b-nat.yaml</code></td>
					<td>A DNAT rule on that VPC</td>
					<td>Created; NSX realized it without the port</td>
			</tr>
			<tr>
					<td><code>test3-workload.yaml</code></td>
					<td>In an existing VPC with a load balancer: a namespace, a subnet, a PVC, a VM group with a boot order, two Ubuntu VMs (cloud-init user with key and hashed password, data disk, subnet, anti-affinity), a LoadBalancer service</td>
					<td>Created in 2 min; SSH through the load balancer as the cloud-init user</td>
			</tr>
			<tr>
					<td><code>test4-vks.yaml</code></td>
					<td>A VKS cluster, one control plane node and one worker, <code>builtin-generic-v3.6.0</code></td>
					<td>Ready in 4 min</td>
			</tr>
			<tr>
					<td><code>test5-ipwait.yaml</code></td>
					<td>One VM whose output is its IP</td>
					<td>Output <code>172.30.0.2</code></td>
			</tr>
	</tbody>
</table>
<h2 id="downloads">Downloads</h2>
<ul>
<li><a href="/files/vcfa-91-blueprint-reference.zip"><code>vcfa-91-blueprint-reference.zip</code></a>:
the five test blueprints; the five base types as VCF Automation&rsquo;s API
returns them; the fifteen palette schemas from the designer and the palette
map; and the field tables of this guide as Markdown.</li>
</ul>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>Self-service on VCF Automation All Apps is only as good as its blueprints.
And a blueprint is only as good as its author&rsquo;s knowledge of the fields,
which are mostly documented somewhere else, or nowhere.</p>
<p>The cost of not knowing shows up late. The designer saves the blueprint,
the validator passes it, and the request fails ten minutes in. Or worse, it
succeeds, with a NAT rule that forwards every port or a firewall rule that
allows any service.</p>
<p>Knowing what the platform actually enforces turns that into a five-second
dry run. It also turns a catalog item from a demo into something a team can
depend on.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>The palette is five resource types. Learn <code>CCI.Supervisor.Resource</code> and
<code>CCI.VPC.Configuration</code> and you can write every item by hand, including
kinds the palette doesn&rsquo;t show.</li>
<li>VCF Automation&rsquo;s validation checks a resource&rsquo;s own properties, never the
manifest or the VPC spec inside. Dry-run manifests against the Supervisor;
test VPC objects by deploying them.</li>
<li>Where the designer&rsquo;s form and the platform disagree, the platform wins:
<code>accessModes</code>, <code>labelSelector</code>, VM affinity terms ending
<code>PreferredDuringExecution</code>.</li>
<li>Outputs are computed once. Wait for what they read: a VM&rsquo;s address needs
the condition <code>VirtualMachineGuestNetworkConfigSynced</code>.</li>
<li>A blueprint VPC has no load balancer and can&rsquo;t get one, so LoadBalancer
services and VKS need a VPC made in the UI.</li>
<li>Name firewall services (<code>&quot;:HTTPS&quot;</code>) instead of port sets, give every rule a
<code>from</code>, and treat a NAT rule as mapping the whole address.</li>
<li>Inline cloud-init passwords are Secret references; <code>count.index</code> needs
<code>allocatePerInstance: true</code>.</li>
</ul>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation.html">Managing Blueprints in VCF Automation</a>: blueprints, the designer, inputs, versions, property groups and custom forms.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/sample-blueprints-in-vcf-automation-for-all-apps.html">Sample Blueprints in VCF Automation</a>: Broadcom&rsquo;s samples: VMs, <code>count</code> with <code>allocatePerInstance</code>, a VM with a VKS cluster, a VPC with a namespace, a VM group with affinity.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/specifying-formatversion-in-your-blueprints.html">Specifying formatVersion in Blueprints</a>: what <code>formatVersion: 2</code> adds, including outputs and <code>__deploymentOverview</code>.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/bindings-and-dependencies.html">Creating bindings and dependencies between resources</a>: <code>dependsOn</code> and property bindings, and how each orders the build.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/property-groups/input-property-groups.html">Input Property Groups</a> and <a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/property-groups/constant-property-groups-in-vcf-automation-for-all-apps.html">Constant Property Groups</a>: <code>${input.&lt;group&gt;.&lt;property&gt;}</code> and <code>${propgroup.&lt;group&gt;.&lt;property&gt;}</code>.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/administering-all-apps-organizations-in-vcfa-automation/managing-secrets-in-vcfa.html">Managing Secrets in VCF Automation</a>: <code>${secret.&lt;name&gt;}</code>, organization and project secrets.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/preparing-for-day-2.html">VCF Automation blueprint designs that prepare for day 2 changes</a>: re-applying a blueprint versus day-2 actions, and bindings in day 2.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-projects-in-vcfa/create-a-namespace-class.html">Create a Namespace Class in VCF Automation</a>: what a namespace class sets, and so what a blueprint namespace must add.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/adding-and-managing-virtual-private-clouds/add-a-vpc.html">Create a Virtual Private Cloud in VCF Automation</a>: a VPC&rsquo;s connectivity profile, private CIDRs and load balancing, and that VKS needs load balancing.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/adding-and-managing-virtual-private-clouds/add-a-vpc/create-a-nat-rule-for-a-vpc-in-vcf-automation(1).html">Create a NAT Rule for a VPC in VCF Automation</a>: the NAT actions, external addresses and priorities behind <code>VPCNATRule</code>.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-security-load-balancing/vdefend/vdefend-firewall/9-1/vcf-automation-integration-with-vdefend-firewall/security-management-workflow.html">Secure North-South boundaries for Transit Gateways and VPCs (vDefend 9.1)</a>: VPC gateway firewall policies, rules realized on the edges, and the activation flag in the security profile.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-service-administration-and-development/9-1/provision-and-manage-virtual-machines/deploying-and-managing-virtual-machines-in-vsphere-iaas-control-plane.html">Deploying and Managing Virtual Machines in vSphere Supervisor</a>: VM classes, images, storage classes and zones, with a pointer to the VM Operator API.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-consumption/latest/managing-vsphere-kuberenetes-service-clusters-and-workloads/provisioning-tkg-service-clusters/using-the-cluster-v1beta1-api/using-the-versioned-clusterclass.html">Using the Versioned ClusterClass</a>: the ClusterClass matrix per VKS release and <code>vmware-system-vks-public</code>.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-consumption/latest/managing-vsphere-kuberenetes-service-clusters-and-workloads/provisioning-tkg-service-clusters/using-the-cluster-v1beta1-api/using-the-versioned-clusterclass/v1beta1-example-default-cluster.html">v1beta1/v1beta2 Example: Default Cluster</a>: the minimum cluster and the CIDR rules.</li>
<li><a href="https://developer.broadcom.com/xapis/vmware-vsphere-kubernetes-service/3.7.0/variable-docs.html">ClusterClass Variable Reference</a>: every variable of the builtin-generic classes, and where each can be overridden.</li>
<li><a href="https://knowledge.broadcom.com/external/article/435137/vm-status-information-missing-in-vcf-aut.html">VM Status Information Missing in VCF Automation 9.0.x Deployments (KB 435137)</a>: where the designer&rsquo;s default VM <code>wait</code> comes from.</li>
</ul>
<p>The VM Operator API itself is documented upstream, outside Broadcom, and
Broadcom&rsquo;s VM Service pages link there: <a href="https://vm-operator.readthedocs.io/en/latest/ref/api/v1alpha5/">v1alpha5 reference</a>.</p>
<hr>
<p><em>Lab environment; opinions my own. Every snippet was validated, dry-run or
deployed on a live VCF 9.1 environment; the field tables come from the
platform&rsquo;s own schemas.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Windows Server 2025 via Aria Automation, part 3: validation as a product, and the details that hurt</title>
      <link>https://thenestedlab.com/posts/windows-2025-aria-part-3/</link>
      <pubDate>Thu, 17 Sep 2026 06:30:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/windows-2025-aria-part-3/</guid>
      <description>A self-contained HTML build report, down to a Gantt chart that spots the reboots. Plus the Session 0 traps, like mangled vmtoolsd arguments and ejecting an ISO with no Explorer, that cost real hours.</description>
      <content:encoded><![CDATA[<p>Most provisioning pipelines end with &ldquo;Deployment completed&rdquo;. This one ends
with a document a human can read. It changed how the platform team and the
server team talk to each other.</p>
<p>Instead of &ldquo;it&rsquo;s built, go check it&rdquo;, the requester gets a page that says
<em>what</em> was built, <em>where</em> it landed, <em>what passed</em>, and <em>how long each step
took</em>.</p>
<p><a href="/posts/windows-2025-aria-part-1/">Part 1</a> was the architecture and <a href="/posts/windows-2025-aria-part-2/">part
2</a> the state machine. This is the payoff.
After it come the details that were nowhere in any documentation.</p>
<h2 id="the-report">The report</h2>
<p><code>06-validate-build.ps1</code> is a pure transformation: <code>data-validation.json</code>
in, one self-contained HTML file out. There&rsquo;s no server and there are no
external assets. CSS, SVG icons and the timeline chart are all inline, so
it opens as-is from the file share or as an email attachment.</p>
<p><img alt="Server Build Validation Report: header, BUILD SUCCESSFUL banner, scorecard (Software 5/5, Build Time 41m 12s, Domain Trust OK, Pending Reboot NO, Disks OK), system information and security cards" loading="lazy" src="/images/ui/a1-aria-report-top.jpg">
<em>The top of the report. Rendered by the real renderer from an anonymised payload: the hostnames, domain and vendor names are fictional, the code that drew it is not. <a href="/files/ACME-LDN-APP-006_Validation_Report.html">Open the full report</a> (it&rsquo;s one self-contained HTML file) or the <a href="/files/ACME-LDN-APP-006_data-validation.json">JSON it was built from</a>.</em></p>
<p>The header shows the VM, execution time, project, deployment and
requester. The banner says <strong>BUILD SUCCESSFUL / BUILD FAILED</strong>, by the
strict rule from part 2. A sticky status bar keeps the hostname and verdict
in view while you scroll. Then:</p>
<table>
	<thead>
			<tr>
					<th>Section</th>
					<th>Contents</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>System</td>
					<td>hostname, OS, latest patch, CPU, RAM, uptime, time zone, pending reboot</td>
			</tr>
			<tr>
					<td>Security &amp; AD</td>
					<td>domain, trust status, OU, NTP source; WinRM/RDP/UAC status cards; local admins</td>
			</tr>
			<tr>
					<td>Software</td>
					<td>one card per agent: pass/fail badge + the individual checks (flag, service, path)</td>
			</tr>
			<tr>
					<td>Network</td>
					<td>per adapter: alias, MAC, IP, mask (converted from prefix), gateway — <strong>joined to the vSphere portgroup</strong> via the guestinfo NIC/MAC map</td>
			</tr>
			<tr>
					<td>Storage</td>
					<td>per volume: capacity bars (warn &lt; 20 % free, critical &lt; 10 %) + backing datastore chips</td>
			</tr>
			<tr>
					<td>Tags</td>
					<td>every deployment tag as a colour chip (palette chosen deterministically by key hash, so the same tag is always the same colour)</td>
			</tr>
			<tr>
					<td>Placement</td>
					<td>vCenter → datacenter → cluster → host chain, VM folder; &ldquo;metadata pending&rdquo; if guestinfo was never readable</td>
			</tr>
			<tr>
					<td><strong>Timeline</strong></td>
					<td>SVG Gantt of every step at true wall-clock position; phase colours; <strong>automatic REBOOT detection</strong> (gaps &gt; 30 s shaded and labelled)</td>
			</tr>
			<tr>
					<td>Installed apps</td>
					<td>collapsible full inventory</td>
			</tr>
	</tbody>
</table>
<p><img alt="Software validation cards (flag, service, path per agent), the network table joined to vSphere portgroups, and storage volumes with capacity bars and datastore chips" loading="lazy" src="/images/ui/a2-aria-report-software-network.jpg">
<em>Sections 3 to 5: one card per agent with its three checks; adapters joined to their portgroups; volumes with their backing datastore.</em></p>
<p>The network table is the one that gets the &ldquo;oh&rdquo; reaction. The guest knows
its adapters and vCenter knows the portgroups. The guestinfo bridge from
part 1 lets one table show both, joined on MAC, with no credentials
crossing the boundary.</p>
<p><img alt="Deployment tags as colour chips and the vCenter to datacenter to cluster to host placement chain with the VM folder" loading="lazy" src="/images/ui/a3-aria-report-tags-placement.jpg">
<em>Tags and placement: all of it from the request and from <code>guestinfo</code>, none of it from a credential in the guest.</em></p>
<p>The Gantt chart is the one operations teams use. When a build takes 90
minutes instead of 40, the chart shows why. It might be the updates step,
or a slow installer. Or it might be a 20-minute gap where the machine sat
at a boot prompt, waiting patiently for someone to notice.</p>
<p><img alt="Provisioning timeline: SVG Gantt with phase colours and the REBOOT gap shaded amber" loading="lazy" src="/images/ui/a4-aria-report-timeline.jpg">
<em>Section 8. Phase colours, true wall-clock positions, and the reboot detected from a gap over 30 seconds — nothing logged &ldquo;rebooting now&rdquo;.</em></p>
<h2 id="the-details-that-hurt">The details that hurt</h2>
<p>Every one of these cost hours, and none of them is in a manual.</p>
<h3 id="vmtoolsd-mangles-arguments-under-system-in-session-0">vmtoolsd mangles arguments under SYSTEM in Session 0</h3>
<p>Reading <code>guestinfo.vra.infrastructure</code> with
<code>&amp; vmtoolsd.exe --cmd &quot;info-get guestinfo.vra.infrastructure&quot;</code> works
interactively. It <strong>fails silently as SYSTEM in a startup task</strong>, because
the argument quoting gets mangled on the way through.</p>
<p>The fix is to build the process explicitly with
<code>System.Diagnostics.Process</code>. Set <code>Arguments</code> as one string, redirect
stdout, and read it yourself. Also retry (15 × 10 s), because the vRO
subscription that writes the value can land <em>after</em> the guest starts
looking.</p>
<h3 id="ejecting-an-iso-with-no-explorer">Ejecting an ISO with no Explorer</h3>
<p>Session 0 has no shell, so <code>Shell.Application</code> ejection does nothing. A
P/Invoke to <code>winmm.dll</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="p">[</span><span class="no">mciSend</span><span class="p">]::</span><span class="n">mciSendString</span><span class="p">(</span><span class="s2">&#34;set cdaudio door open&#34;</span><span class="p">,</span> <span class="vm">$null</span><span class="p">,</span> <span class="mf">0</span><span class="p">,</span> <span class="p">[</span><span class="no">IntPtr</span><span class="p">]::</span><span class="n">Zero</span><span class="p">)</span>
</span></span></code></pre></div><p>opens the tray. It feels like 1998. It works.</p>
<h3 id="exit-1003-is-the-only-reboot-you-should-ever-request-from-cloudbase-init">Exit 1003 is the only reboot you should ever request from cloudbase-init</h3>
<p>Call <code>Restart-Computer</code> from a cloudbase-init script, and you race the
plugin&rsquo;s own state tracking. Exit <strong>1003</strong> instead. cloudbase-init reboots,
runs the part again on next boot, and your flag file short-circuits it.</p>
<p>The installers&rsquo; <strong>3010</strong> is a different animal. That one means &ldquo;success,
reboot wanted&rdquo;, and the build master decides when.</p>
<h3 id="cloudbase-init-has-to-remove-itself">cloudbase-init has to remove itself</h3>
<p>A delivered server with cloudbase-init still installed has an unattended
execution surface. Anyone who can present a config drive owns the box.</p>
<p>The cleanup phase stops the service, kills the processes, runs the
uninstaller silently and deletes the directory. It does all that <em>before</em>
validation, so the report can confirm it&rsquo;s gone.</p>
<h3 id="uac-was-off-turn-it-back-on">UAC was off. Turn it back on.</h3>
<p>The base image relaxes <code>EnableLUA</code> and <code>FilterAdministratorToken</code> so the
build runs without prompts. If the cleanup forgets to restore them, you
ship a server with UAC disabled and a report that says SUCCESS with a
perfectly straight face. The &ldquo;UAC enabled&rdquo; card in the security section
exists so this can never be silent.</p>
<h3 id="secrets-encrypt-to-the-machine-then-scrub">Secrets: encrypt to the machine, then scrub</h3>
<p>The two share passwords are the only secrets that ever touch the guest&rsquo;s
disk. They&rsquo;re AES-encrypted with a key derived from the BIOS UUID
(<code>Win32_ComputerSystemProduct.UUID</code>), so they&rsquo;re useless off the box.
They&rsquo;re decrypted only in memory, and overwritten with <code>*** SCRUBBED ***</code>
before the final reboot.</p>
<p>That&rsquo;s the right containment for a <em>transient</em> build secret. It&rsquo;s not a
vault, and shouldn&rsquo;t be described as one.</p>
<h3 id="the-requester-is-told-too-early">The requester is told too early</h3>
<p>The &ldquo;your deployment completed&rdquo; email fires at Compute Post Provision.
That&rsquo;s when vCenter has finished, not when the guest has. So users open a
server that&rsquo;s mid-Windows-Updates, which is nobody&rsquo;s idea of a warm
welcome. Move the email to a deployment-completion topic, or at least
include the report&rsquo;s future share path.</p>
<h3 id="hostname-allocation-has-a-race">Hostname allocation has a race</h3>
<p>Read-then-allocate against AD isn&rsquo;t synchronised. Two deployments running
at the same time can see the same highest suffix and pick the same name.
Within one multi-machine deployment, the count-based batch is safe. Across
deployments, wrap the search-and-generate in a vRO <code>LockingSystem</code> lock.</p>
<h2 id="what-id-carry-to-any-build-pipeline">What I&rsquo;d carry to any build pipeline</h2>
<p>Strip out the Windows specifics (a fair few of them reboots), and five
ideas survive:</p>
<ol>
<li><strong>The report is the deliverable.</strong> Build it from a single JSON document
so it&rsquo;s testable without a build.</li>
<li><strong>Join guest facts to platform facts</strong> via a one-way metadata channel.</li>
<li><strong>Detect reboots from timing gaps</strong>, not from logging &ldquo;rebooting now&rdquo;.</li>
<li><strong>Health is three checks</strong>, never the installer&rsquo;s exit code.</li>
<li><strong>Clean up before you validate</strong>, so &ldquo;clean&rdquo; is a checkable claim.</li>
</ol>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>The report is the part customers remember. It replaces &ldquo;your server is
ready&rdquo; with evidence:</p>
<ul>
<li>what was installed, and whether it&rsquo;s healthy;</li>
<li>where the server landed in vCenter;</li>
<li>how the network was configured;</li>
<li>how long each step took, and where the reboots were.</li>
</ul>
<p>Service desks use it to close the request. Security teams use it to
confirm the controls, and platform teams use the timeline to spot
regressions. The same idea (validate, then publish proof) carries over to
any provisioning pipeline, Windows or not.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>Ship a <strong>report</strong>, not a status. Self-contained HTML, one JSON source.</li>
<li>Under SYSTEM in Session 0: build processes explicitly, eject media via
<code>mciSendString</code>, expect no shell.</li>
<li><strong>Exit 1003</strong> for cloudbase-init reboots; <strong>3010</strong> is the installer&rsquo;s
word for &ldquo;later&rdquo;.</li>
<li>Remove cloudbase-init and restore UAC, and make both of them <em>checks</em> in
the report.</li>
<li>Machine-keyed encryption + scrub is right for transient secrets. Say
what it is.</li>
<li>Fix the two timing bugs: the early requester email, and the hostname
race under parallel deployments.</li>
</ul>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/aria/aria-automation/8-18/assembler-on-prem-using-and-managing-master-map-8-18/maphead-designing-your-deployments/maphead-extensibility-in-cloud-assembly/learn-more-about-extensibilty-subscriptions/event-topics-provided-with-cloud-assembly.html">Event topics provided with Automation Assembler</a>: Compute post provision against Deployment completed, for the requester&rsquo;s email</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/aria/aria-automation/8-18/assembler-on-prem-using-and-managing-master-map-8-18/maphead-designing-your-deployments/maphead-extensibility-in-cloud-assembly/learn-more-about-extensibilty-subscriptions/create-an-extensibility-subscription.html">Create an extensibility subscription</a>: subscribing an Orchestrator workflow to an event topic, with a filter and blocking</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vsphere/tools/12-5-0/vmware-tools-administration-12-5-0/configuring-vmware-tools-components/using-vmware-tools-configuration-utility/view-virtual-machine-status-information/query-information-using-guestinfo-variable.html">Query Information using GuestInfo Variable</a>: <code>vmtoolsd --cmd</code> with <code>info-get</code> and <code>info-set</code> for <code>guestinfo</code> variables</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/aria/aria-automation/8-18/assembler-on-prem-using-and-managing-master-map-8-18/maphead-designing-your-deployments/initialize-general/initialize-windows-general/initialize-windows-image-vsphere.html">Windows Automation Assembler image for vSphere</a>: how Cloudbase-Init is installed on the template, before the build removes it</li>
</ul>
<p><em>Previously: <a href="/posts/windows-2025-aria-part-2/">the state machine</a>. This VM
is also where the <a href="/posts/telegraf-windows-2025/">Telegraf</a> and
<a href="/posts/fluent-bit-two-ways/">fluent-bit</a> Windows agents land.</em></p>
<hr>
<p><em>Lab write-up of a production pattern; customer specifics removed. Opinions
my own.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Windows Server 2025 via Aria Automation, part 2: a state machine that survives four reboots</title>
      <link>https://thenestedlab.com/posts/windows-2025-aria-part-2/</link>
      <pubDate>Thu, 17 Sep 2026 06:20:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/windows-2025-aria-part-2/</guid>
      <description>One script runs at every startup until the build is done. Flag files make each step run once, a state file keeps true timings across reboots, and at the end it cleans up, reports and deletes itself.</description>
      <content:encoded><![CDATA[<p><a href="/posts/windows-2025-aria-part-1/">Part 1</a> ended with a startup scheduled
task registered and <code>05-build-master.ps1</code> staged locally. From here, the
machine reboots at least twice more. Windows Updates insists, as it tends
to, and the build ends with a clean reboot.</p>
<p>Every one of those boots runs the same script. So the script has to
<em>converge</em> on a finished build, however many times it runs.</p>
<p>That&rsquo;s a state machine, and it&rsquo;s built from three very boring mechanisms.</p>
<h2 id="the-three-mechanisms">The three mechanisms</h2>
<p><strong>1. Master kill switch.</strong> If <code>100_build_complete.flag</code> exists, exit
straight away. A stray task run after completion does nothing.</p>
<p><strong>2. Per-step flags.</strong> Every install step writes
<code>NN_&lt;Step&gt;_installed.flag</code> when it succeeds, and is skipped on later runs.</p>
<p><strong>3. Persisted state.</strong> <code>data-state.json</code> holds the provisioning start
time and the timing of each step, and it&rsquo;s reloaded on every boot. So the
final report shows the <em>true</em> duration of every step across the whole
build, not just the last boot. A step seen again as SKIPPED after a reboot
never overwrites its real recorded duration.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="n">every</span> <span class="n">boot</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="mi">100</span><span class="n">_build_complete</span><span class="o">.</span><span class="n">flag</span> <span class="err">→</span> <span class="n">exit</span>
</span></span><span class="line"><span class="cl">  <span class="nb">load</span> <span class="n">data</span><span class="o">-</span><span class="n">state</span><span class="o">.</span><span class="n">json</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="n">step</span> <span class="ow">in</span> <span class="o">$</span><span class="n">softwarePayload</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="n">NN_step_installed</span><span class="o">.</span><span class="n">flag</span> <span class="err">→</span> <span class="n">SKIP</span> <span class="p">(</span><span class="n">keep</span> <span class="n">recorded</span> <span class="n">timing</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">run</span> <span class="n">installer</span> <span class="n">from</span> <span class="n">local</span> <span class="n">Staging</span> <span class="p">(</span><span class="n">timeout</span> <span class="n">ceiling</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">exit</span> <span class="mi">0</span> <span class="ow">or</span> <span class="mi">3010</span> <span class="err">→</span> <span class="n">write</span> <span class="n">flag</span><span class="p">,</span> <span class="n">record</span> <span class="n">timing</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="n">step</span><span class="o">.</span><span class="n">RebootAfter</span> <span class="err">→</span> <span class="n">Restart</span><span class="o">-</span><span class="n">Computer</span><span class="p">;</span> <span class="n">exit</span>      <span class="err">←</span> <span class="n">next</span> <span class="n">boot</span> <span class="n">resumes</span> <span class="n">here</span>
</span></span><span class="line"><span class="cl">  <span class="n">cleanup</span> <span class="err">→</span> <span class="n">validate</span> <span class="err">→</span> <span class="n">publish</span> <span class="err">→</span> <span class="bp">self</span><span class="o">-</span><span class="n">destruct</span> <span class="err">→</span> <span class="n">final</span> <span class="n">reboot</span>
</span></span></code></pre></div><p><img alt="The same script on three boots: boot 3 installs the agents and Windows Updates, then reboots; boot 4 skips the flagged steps, cleans up, validates, reports and self-destructs; boot 5 is a clean server, where a stray run exits at once" loading="lazy" src="/images/diagrams/windows-state-machine.svg">
<em>The loop above, boot by boot.</em></p>
<h2 id="phase-1-software-declaratively">Phase 1: software, declaratively</h2>
<p>The stack is a single array, and that array is the designed extension
point:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nv">$softwarePayload</span> <span class="p">=</span> <span class="vm">@</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="vm">@</span><span class="p">{</span> <span class="n">Name</span><span class="p">=</span><span class="s1">&#39;MonitoringAgent&#39;</span><span class="p">;</span> <span class="n">Folder</span><span class="p">=</span><span class="nv">$p</span><span class="p">.</span><span class="n">monPath</span><span class="p">;</span>  <span class="n">File</span><span class="p">=</span><span class="nv">$p</span><span class="p">.</span><span class="n">monInstallFile</span><span class="p">;</span>  <span class="n">Timeout</span><span class="p">=</span><span class="mf">20</span><span class="p">;</span> <span class="n">Reboot</span><span class="p">=</span><span class="vm">$false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">     <span class="n">Service</span><span class="p">=</span><span class="s1">&#39;HealthService&#39;</span><span class="p">;</span>       <span class="n">Path</span><span class="p">=</span><span class="s1">&#39;...\Monitoring Agent\HealthService.exe&#39;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="vm">@</span><span class="p">{</span> <span class="n">Name</span><span class="p">=</span><span class="s1">&#39;InventoryAgent&#39;</span><span class="p">;</span>  <span class="n">Folder</span><span class="p">=</span><span class="nv">$p</span><span class="p">.</span><span class="n">invPath</span><span class="p">;</span>  <span class="n">File</span><span class="p">=</span><span class="nv">$p</span><span class="p">.</span><span class="n">invInstallFile</span><span class="p">;</span>  <span class="n">Timeout</span><span class="p">=</span><span class="mf">15</span><span class="p">;</span> <span class="n">Reboot</span><span class="p">=</span><span class="vm">$false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">     <span class="n">Service</span><span class="p">=</span><span class="s1">&#39;InventoryAgent&#39;</span><span class="p">;</span>      <span class="n">Path</span><span class="p">=</span><span class="s1">&#39;...\Inventory\agent.exe&#39;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="vm">@</span><span class="p">{</span> <span class="n">Name</span><span class="p">=</span><span class="s1">&#39;EndpointSecurity&#39;</span><span class="p">;</span><span class="n">Folder</span><span class="p">=</span><span class="nv">$p</span><span class="p">.</span><span class="n">epsPath</span><span class="p">;</span>  <span class="n">File</span><span class="p">=</span><span class="nv">$p</span><span class="p">.</span><span class="n">epsInstallFile</span><span class="p">;</span>  <span class="n">Timeout</span><span class="p">=</span><span class="mf">30</span><span class="p">;</span> <span class="n">Reboot</span><span class="p">=</span><span class="vm">$false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">     <span class="n">Service</span><span class="p">=</span><span class="s1">&#39;masvc&#39;</span><span class="p">;</span>               <span class="n">Path</span><span class="p">=</span><span class="s1">&#39;...\Agent\masvc.exe&#39;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="vm">@</span><span class="p">{</span> <span class="n">Name</span><span class="p">=</span><span class="s1">&#39;LogAgent&#39;</span><span class="p">;</span>        <span class="n">Folder</span><span class="p">=</span><span class="nv">$p</span><span class="p">.</span><span class="n">logPath</span><span class="p">;</span>  <span class="n">File</span><span class="p">=</span><span class="nv">$p</span><span class="p">.</span><span class="n">logInstallFile</span><span class="p">;</span>  <span class="n">Timeout</span><span class="p">=</span><span class="mf">15</span><span class="p">;</span> <span class="n">Reboot</span><span class="p">=</span><span class="vm">$false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">     <span class="n">Service</span><span class="p">=</span><span class="s1">&#39;LogAgentService&#39;</span><span class="p">;</span>     <span class="n">Path</span><span class="p">=</span><span class="s1">&#39;...\Log Agent\liwinsvc.exe&#39;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="vm">@</span><span class="p">{</span> <span class="n">Name</span><span class="p">=</span><span class="s1">&#39;WindowsUpdates&#39;</span><span class="p">;</span>  <span class="n">Folder</span><span class="p">=</span><span class="nv">$p</span><span class="p">.</span><span class="n">updPath</span><span class="p">;</span>  <span class="n">File</span><span class="p">=</span><span class="nv">$p</span><span class="p">.</span><span class="n">updInstallFile</span><span class="p">;</span>  <span class="n">Timeout</span><span class="p">=</span><span class="mf">45</span><span class="p">;</span> <span class="n">Reboot</span><span class="p">=</span><span class="vm">$true</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><p>Adding a product means adding an element. Each installer runs from the
local staging copy, with a timeout in case it hangs. Exit codes 0 and
<strong>3010</strong> (success, reboot required) both count as success.</p>
<p>When a <code>Reboot=$true</code> step succeeds, the script restarts the machine and
exits. On the next boot the task runs it again. The completed steps skip
via their flags, and the run carries on at the next step.</p>
<p>Post-install health isn&rsquo;t &ldquo;the installer said OK&rdquo;. Installers say OK with
great confidence and variable accuracy. Health is <strong>flag present AND
service running AND install path exists</strong>, with a 120-second wait for
delayed-start services. Windows Updates is flag-only, as there&rsquo;s no
service to check.</p>
<h2 id="phase-2-cleanup--leave-nothing-behind">Phase 2: cleanup — leave nothing behind</h2>
<ul>
<li><strong>Eject the config-drive ISO.</strong> Session 0 has no Explorer, so the usual
shell ejection doesn&rsquo;t work. A P/Invoke to <code>winmm</code>&rsquo;s
<code>mciSendString(&quot;set cdaudio door open&quot;)</code> does.</li>
<li><strong>Restore UAC.</strong> <code>EnableLUA</code> and <code>FilterAdministratorToken</code> were relaxed
in the base image so the build could run unattended. Turn both back on.</li>
<li><strong>Uninstall cloudbase-init.</strong> Stop and delete the service, kill its
processes, run the uninstaller silently and remove the directory.
Guarded by <code>99_cleanup_complete.flag</code>.</li>
</ul>
<p>The startup task is deleted <em>before</em> validation. That way the health check
can truthfully report &ldquo;no automation task remains&rdquo;.</p>
<h2 id="phase-3-collect-the-evidence">Phase 3: collect the evidence</h2>
<p>The script assembles <code>data-validation.json</code>, the single input to the
report in <a href="/posts/windows-2025-aria-part-3/">part 3</a>:</p>
<table>
	<thead>
			<tr>
					<th>Collector</th>
					<th>Captures</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Network</td>
					<td>per active adapter: alias, IPv4, prefix, gateway, MAC, DNS</td>
			</tr>
			<tr>
					<td>Infrastructure</td>
					<td><code>guestinfo.vra.infrastructure</code> via <code>vmtoolsd</code>, <strong>15 attempts × 10 s</strong> (the vRO subscription can land late)</td>
			</tr>
			<tr>
					<td>OS / hardware</td>
					<td>domain, CPUs, RAM, caption, uptime, time zone, pending reboot, latest hotfix</td>
			</tr>
			<tr>
					<td>AD / security</td>
					<td>local Administrators, secure-channel test, machine OU</td>
			</tr>
			<tr>
					<td>Disks</td>
					<td>per volume: letter, label, size, free</td>
			</tr>
			<tr>
					<td>Post-build health</td>
					<td>WinRM, RDP, UAC enabled, startup task gone, domain DNS resolves, NTP source</td>
			</tr>
			<tr>
					<td>Software</td>
					<td>per app: flag + service + path</td>
			</tr>
			<tr>
					<td>Installed apps</td>
					<td>both uninstall hives (64-bit and WOW6432)</td>
			</tr>
	</tbody>
</table>
<p>The verdict is strict: <strong><code>GuestStatus = Success</code> only if every software
item is healthy <em>and</em> the machine is domain-joined.</strong> Anything else turns
the report banner red. The report doesn&rsquo;t do &ldquo;mostly fine&rdquo;.</p>
<h2 id="phases-4-and-5-publish-then-self-destruct">Phases 4 and 5: publish, then self-destruct</h2>
<p>Remap the share with the <strong>write</strong> account, with 5 × 10 s retries. It&rsquo;s a
different account from the read-only one used for pulls, so a compromised
build guest can&rsquo;t tamper with the engine.</p>
<p>Render the HTML report locally. Write <code>100_build_complete.flag</code>: kill
switch armed. Copy the report plus <code>Data\</code>, <code>Flags\</code> and <code>Logs\</code> to
<code>\\share\Builds\&lt;image&gt;\&lt;HOSTNAME&gt;\</code>. Stop the transcript <em>before</em> copying
the logs, so the final log is complete and unlocked.</p>
<p>Then:</p>
<ul>
<li>delete the startup task;</li>
<li>overwrite both share passwords in the on-disk payload with
<code>*** SCRUBBED ***</code>;</li>
<li>delete <code>Data\</code> and <code>Staging\</code> (and <code>Flags\</code> and <code>Logs\</code>, if the share
copy succeeded);</li>
<li>delete the sibling scripts and itself;</li>
<li>remove <code>Scripts\</code>;</li>
<li>reboot one final time.</li>
</ul>
<p>The delivered server boots clean and domain-joined, with its agents
running. It carries <strong>no credentials, payloads or tooling</strong>.</p>
<h2 id="the-reboot-sequence-of-a-nominal-build">The reboot sequence of a nominal build</h2>
<ol>
<li>After static networking (<code>00</code>, exit 1003)</li>
<li>After the engine pull (<code>03</code>, exit 1003), which ends the cloudbase-init
phase</li>
<li>After Windows Updates (<code>05</code>, <code>Reboot=$true</code>)</li>
<li>Final, after publish and self-destruct</li>
</ol>
<p>Four reboots, and only one of them is Windows Updates&rsquo; doing. We asked for
the other three.</p>
<p>The report&rsquo;s timeline chart finds the update reboot by itself. Any gap
over 30 seconds between steps is shaded and labelled REBOOT.</p>
<p>When it&rsquo;s done, the <code>Flags\</code> folder is the whole history of the build, in
file names:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="n">Flags</span>  <span class="mi">00</span><span class="n">_config</span><span class="o">-</span><span class="n">network</span><span class="o">.</span><span class="n">flag</span>
</span></span><span class="line"><span class="cl">  <span class="mi">01</span><span class="n">_config</span><span class="o">-</span><span class="n">disks</span><span class="o">.</span><span class="n">flag</span>
</span></span><span class="line"><span class="cl">  <span class="mi">02</span><span class="n">_join</span><span class="o">-</span><span class="n">domain</span><span class="o">.</span><span class="n">flag</span>
</span></span><span class="line"><span class="cl">  <span class="mi">03</span><span class="n">_init</span><span class="o">-</span><span class="n">puller</span><span class="o">.</span><span class="n">flag</span>
</span></span><span class="line"><span class="cl">  <span class="mi">04</span><span class="n">_stage</span><span class="o">-</span><span class="n">payloads</span><span class="o">.</span><span class="n">flag</span>
</span></span><span class="line"><span class="cl">  <span class="mi">01</span><span class="n">_MonitoringAgent_installed</span><span class="o">.</span><span class="n">flag</span>
</span></span><span class="line"><span class="cl">  <span class="mi">02</span><span class="n">_InventoryAgent_installed</span><span class="o">.</span><span class="n">flag</span>
</span></span><span class="line"><span class="cl">  <span class="mi">03</span><span class="n">_EndpointSecurity_installed</span><span class="o">.</span><span class="n">flag</span>
</span></span><span class="line"><span class="cl">  <span class="mi">04</span><span class="n">_LogAgent_installed</span><span class="o">.</span><span class="n">flag</span>
</span></span><span class="line"><span class="cl">  <span class="mi">05</span><span class="n">_WindowsUpdates_installed</span><span class="o">.</span><span class="n">flag</span>
</span></span><span class="line"><span class="cl">  <span class="mi">99</span><span class="n">_cleanup_complete</span><span class="o">.</span><span class="n">flag</span>
</span></span><span class="line"><span class="cl">  <span class="mi">100</span><span class="n">_build_complete</span><span class="o">.</span><span class="n">flag</span>        <span class="o">&lt;-</span> <span class="n">kill</span> <span class="k">switch</span>
</span></span></code></pre></div><p>And the persisted timings turn into this, in the report from <a href="/posts/windows-2025-aria-part-3/">part
3</a>:</p>
<p><img alt="Provisioning timeline from the build report: five install steps, a shaded four-minute REBOOT gap after Windows Updates, then cleanup, collection and write steps" loading="lazy" src="/images/ui/a4-aria-report-timeline.jpg">
<em>Every bar sits at its true wall-clock position across the reboots because the state file carried the timings. The amber band is the reboot after Windows Updates, found automatically from the gap.</em></p>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>What customers get from a reboot-safe build is predictability. Every
server takes the same steps in the same order, survives the reboots
Windows insists on, and finishes clean. The delivered machine has no
tooling, no credentials and no leftover tasks.</p>
<p>That last part matters to security reviewers as much as the first part
matters to operations. And because every step&rsquo;s timing is recorded, &ldquo;why
did this build take twice as long?&rdquo; has an answer instead of a guess.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>A reboot-safe build is <strong>kill switch + per-step flags + persisted
timings</strong>. Nothing cleverer is needed.</li>
<li>Treat exit <strong>3010</strong> as success. Let the <em>step</em> declare whether to
reboot; the loop handles it.</li>
<li>Health = flag <strong>and</strong> service <strong>and</strong> path. Installer exit codes lie.</li>
<li>Read platform-injected metadata with <strong>retries</strong>. The workflow that
injects it may land after the guest starts looking.</li>
<li>Two share accounts: read-only for pulls, write-only for publishing.</li>
<li>Delete the task before validating, so &ldquo;no task remains&rdquo; is checkable.</li>
<li>Stop the transcript before you copy the logs.</li>
<li>Scrub, delete yourself, reboot. The customer gets a server, not a
build environment.</li>
</ul>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/aria/aria-automation/8-18/assembler-on-prem-using-and-managing-master-map-8-18/maphead-designing-your-deployments/initialize-general/initialize-windows-general/initialize-windows-image-vsphere.html">Windows Automation Assembler image for vSphere</a>: how Cloudbase-Init gets onto the template, the install the cleanup removes</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/aria/aria-automation/8-18/assembler-on-prem-using-and-managing-master-map-8-18/maphead-designing-your-deployments/maphead-extensibility-in-cloud-assembly/learn-more-about-extensibilty-subscriptions/event-topics-provided-with-cloud-assembly.html">Event topics provided with Automation Assembler</a>: Compute post provision, issued once per machine after it is provisioned</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/aria/aria-automation/8-18/assembler-on-prem-using-and-managing-master-map-8-18/maphead-designing-your-deployments/maphead-extensibility-in-cloud-assembly/extensibility-workflow-subscriptions/learn-more-about-workflow-subscriptions/how-do-i-track-workflow-runs.html">How do I track workflow runs</a>: Extensibility &gt; Activity &gt; Workflow Runs, to see when the metadata workflow ran</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vsphere/tools/12-5-0/vmware-tools-administration-12-5-0/configuring-vmware-tools-components/using-vmware-tools-configuration-utility/view-virtual-machine-status-information/query-information-using-guestinfo-variable.html">Query Information using GuestInfo Variable</a>: <code>info-get</code> on a <code>guestinfo</code> variable from inside the guest</li>
</ul>
<p><em>Part 3: <a href="/posts/windows-2025-aria-part-3/">validation as a product, and the details that hurt</a>.</em></p>
<hr>
<p><em>Lab write-up of a production pattern; customer specifics removed. Opinions
my own.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Windows Server 2025 via Aria Automation, part 1: the pipeline</title>
      <link>https://thenestedlab.com/posts/windows-2025-aria-part-1/</link>
      <pubDate>Thu, 17 Sep 2026 06:10:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/windows-2025-aria-part-1/</guid>
      <description>One catalog request, one fully built, domain-joined Windows Server 2025 with its agents, a validation report, and no trace of the tooling that built it. The three-layer design, and why each seam is where it is.</description>
      <content:encoded><![CDATA[<p>A user fills in a form: environment, size, disks, networks, tags. Some time
later there&rsquo;s a Windows Server 2025 VM with:</p>
<ul>
<li>a sequential hostname, allocated from Active Directory (AD);</li>
<li>static IPs on up to four NICs;</li>
<li>its data disks laid out and lettered;</li>
<li>domain membership;</li>
<li>the standard agent stack, installed and healthy;</li>
<li>a pixel-perfect HTML build report on a file share.</li>
</ul>
<p>And there&rsquo;s <strong>none of the automation tooling left on the box</strong>. The build
behaves like the ideal house guest: it makes itself useful, tidies up and
leaves.</p>
<p>This three-part series is how that works. It&rsquo;s a VM Apps build: a classic
Aria Automation cloud template plus vRO (Aria Automation Orchestrator). It&rsquo;s
also a good example of the pattern. The platform does the allocation and
the metadata; the guest does the guest.</p>
<p>Part 1 is the architecture. <a href="/posts/windows-2025-aria-part-2/">Part 2</a> is the
state machine that survives four reboots. <a href="/posts/windows-2025-aria-part-3/">Part 3</a>
is the validation report and the details that hurt.</p>
<blockquote>
<p>Customer-specific names, products and policies are generalised throughout.
The patterns are the point.</p>
</blockquote>
<h2 id="three-layers-three-reasons">Three layers, three reasons</h2>
<p><img alt="Three layers: Aria Automation and vRO allocate the hostname and pass placement data into the guest; cloudbase-init sets the network, the disks and the domain join and pulls the engine across two reboots; the file-share engine stages, installs, validates, reports and removes itself" loading="lazy" src="/images/diagrams/windows-pipeline-three-layers.svg">
<em>Each layer owns what only it can do, and no vCenter or AD-admin credential ever enters the guest.</em></p>
<p>Each seam exists for a reason you can state in one sentence:</p>
<ul>
<li><strong>Aria/vRO owns what needs platform credentials:</strong> querying AD for the
next hostname, and reading vCenter for where the VM landed. The guest
never holds a vCenter or AD-admin credential.</li>
<li><strong>cloudbase-init owns what must happen before anything else.</strong> The
network has to work before the share can be reached. The disk layout has
to exist before installers land on it. And the domain join changes the
security context that everything after it runs in. That last one is the
whole reason the next layer exists (more on that below).</li>
<li><strong>The engine is pulled, not embedded.</strong> Installers change and agents get
new versions, and re-releasing a cloud template for every payload update
is how automation dies. Update a script on the share, and every build
after that gets it.</li>
</ul>
<p>vCenter guest customization is <strong>disabled</strong> (<a href="https://techdocs.broadcom.com/us/en/vmware-cis/aria/aria-automation/8-18/assembler-on-prem-using-and-managing-master-map-8-18/maphead-designing-your-deployments/initialize-general/initialize-vsphere-static-ips.html"><code>customizeGuestOs: false</code></a>).
One code path owns hostname, networking, disks and join. Two would fight.</p>
<h2 id="the-control-plane-two-vro-hooks-that-matter">The control plane: two vRO hooks that matter</h2>
<p><strong>Compute Allocation: a sequential hostname.</strong> The template builds a prefix
from the location, classification, environment and application inputs. A
vRO workflow queries AD computer objects with that prefix and allocates
<em>highest existing suffix + 1</em>.</p>
<p>Gaps are never reused. With <code>-001</code>, <code>-002</code>, <code>-003</code> and <code>-005</code> present, the
next is <code>-006</code>. A gap usually means a deleted machine, and deleted machines
can be less gone than we&rsquo;d like. The old name may still live in DNS, a backup
catalogue or the configuration management database (CMDB). Reusing it is
how you restore the wrong server.</p>
<p><strong>Compute Post Provision: placement metadata into the guest.</strong> A second
workflow asks vCenter where the VM actually landed: vCenter, datacenter,
cluster, host, folder, datastores, and which portgroup each NIC is on. It
writes all that as JSON into the VM&rsquo;s <code>extraConfig</code>, as
<code>guestinfo.vra.infrastructure</code>.</p>
<p>Later, inside the guest,
<code>vmtoolsd.exe --cmd &quot;info-get guestinfo.vra.infrastructure&quot;</code> reads it back.
That&rsquo;s the bridge that lets the build report say &ldquo;this VM is on host X,
datastore Y&rdquo; with <strong>zero vCenter credentials in the guest</strong>.</p>
<p>Three more Post Provision workflows email the backup team, the security
operations centre (SOC) and the requester. (A note for part 3: the
requester&rsquo;s &ldquo;your deployment has completed&rdquo; email fires here, before the
in-guest build has even started.)</p>
<h2 id="first-boot-cloudbase-init-four-scripts-two-reboots">First boot: cloudbase-init, four scripts, two reboots</h2>
<p>The template&rsquo;s <code>cloudConfig</code> is a MIME multipart. It holds one
<code>cloud-config</code> part (set the hostname, write the tags to disk) and four
<code>#ps1_sysnative</code> scripts, run in order. Three conventions make them safe to
re-run (idempotent):</p>
<ul>
<li><strong>Flag files</strong> under <code>Flags\</code>. A script exits straight away if its flag
exists, so re-runs after a reboot do nothing.</li>
<li><strong>Transcripts</strong> under <code>Logs\</code>, one per script.</li>
<li><strong>Exit 1003</strong> means &ldquo;reboot me and run this part again on next boot&rdquo; (the
flag then short-circuits it). Exit 0 means done. Exit 1 means failure.</li>
</ul>
<table>
	<thead>
			<tr>
					<th>Script</th>
					<th>Does</th>
					<th>Exit</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>00-config-network</code></td>
					<td>pairs adapters (by ifIndex) with <code>to_json(self.networks)</code> (by deviceIndex); static IP/gateway/DNS per NIC</td>
					<td><strong>1003</strong> — reboot 1</td>
			</tr>
			<tr>
					<td><code>01-config-disks</code></td>
					<td>extends C:, onlines/initialises/partitions/formats each extra disk from the request array</td>
					<td>0</td>
			</tr>
			<tr>
					<td><code>02-join-domain</code></td>
					<td>runs the join script via a one-shot scheduled task as local admin; verifies <code>CsDomain ≠ WORKGROUP</code></td>
					<td>0</td>
			</tr>
			<tr>
					<td><code>03-init-puller</code></td>
					<td>writes <code>data-payload.json</code>, encrypts the share secrets, pulls 04/05/06 from the share, launches staging</td>
					<td><strong>1003</strong> — reboot 2</td>
			</tr>
	</tbody>
</table>
<p>That&rsquo;s two reboots before a single agent has been installed. By Windows
standards, it&rsquo;s barely clearing its throat.</p>
<p>The network script carries an assumption worth writing on the wall. It
pairs OS adapters with the request&rsquo;s NICs <strong>by position</strong>. That holds for
freshly cloned VMs where the NICs were added in PCI order. If anyone ever
changes the NIC order after cloning, revisit it.</p>
<h2 id="why-not-just-cloudbase-init-or-guest-customization-all-the-way-down">Why not just cloudbase-init (or guest customization) all the way down?</h2>
<p>The obvious design is the one we started with: let the platform do the
guest. vCenter guest customization sets the hostname and IP and joins the
domain. Then one cloudbase-init userdata installs the agents and patches,
and reports back. No scheduled tasks, no pulled engine, no state machine.</p>
<p>It works on a workgroup machine. It stops working the moment the machine
joins a real domain, and it stops in a way that is easy to misread.</p>
<p><strong>The domain join changes the rules mid-build.</strong> Everything up to the join
runs as a fresh, local, unmanaged Windows install. That means the local
Administrator, the default execution policy, and no central policy.</p>
<p>At the join, the machine lands in its target organisational unit (OU). On
the next policy refresh, which the reboot guarantees, <strong>Group Policy
applies</strong>. In this environment, the policy for member servers includes the
usual security baseline:</p>
<ul>
<li>controls on script execution;</li>
<li>limits on what may run, from where, and under which accounts;</li>
<li>hardening of the local administrator context.</li>
</ul>
<p>None of that is negotiable, and none of it should be. It&rsquo;s the same policy
every production server gets.</p>
<p><strong>What that does to a first-boot pipeline.</strong> cloudbase-init runs its
plugins as a service, as LocalSystem, running scripts from its own
directory. Before the join, that context can do anything. After the join,
it&rsquo;s exactly the kind of context the baseline is designed to restrict.</p>
<p>So everything that comes <em>after</em> the join is in trouble: the installer
pulls, the agent installs, the reboots, the validation. They either fail
outright or, worse, quietly do nothing. cloudbase-init logs the plugin as
executed, the script never ran anything, and the build &ldquo;completes&rdquo; with an
unpatched server carrying no agents.</p>
<p>The first few builds looked exactly like that. The log was technically
correct, which is the least useful kind of correct.</p>
<p><strong>Two ways out, one of them wrong.</strong> You can relax policy for the build: a
staging OU with a weaker baseline, a Group Policy Object (GPO) exemption
for the cloudbase-init path, or a delayed join. But then the server is
built under one set of rules and delivered under another. And the join
becomes a late step that nothing after it exercises.</p>
<p>Or you can accept the policy as the environment it is, and run the
post-join work in a context the policy <em>permits</em>.</p>
<p><strong>The permitted context is a scheduled task under an explicit identity.</strong>
Each task runs as a named account: a domain-joined local admin for the pull
and the join check, and SYSTEM for the build master. It runs at the highest
run level, with <code>-ExecutionPolicy Bypass</code> on each call, from a staging path
the policy allows. That&rsquo;s an ordinary, auditable pattern, and the baseline
was written to allow for it.</p>
<p>So cloudbase-init doesn&rsquo;t run the OS commands itself; it hands them to
tasks. Its job shrinks to &ldquo;get the network up, lay out disks, join, hand
off&rdquo;. The design also picks up three things it now depends on:</p>
<ul>
<li>a <strong>per-task execution ceiling</strong> (1 h for the pull, 2 h for the build)
that contains a hung installer instead of leaving a half-built VM;</li>
<li>an <strong>at-startup trigger</strong>, which is what makes a multi-reboot state
machine possible after cloudbase-init has been uninstalled;</li>
<li>a clean <strong>security story</strong>: the identities that do the work are the
ones the domain already governs, and they stop existing on the box when
the build is done.</li>
</ul>
<p><strong>Why not Aria&rsquo;s own in-guest mechanisms?</strong> Guest customization only
covers hostname, IP and join. Letting it <em>and</em> cloudbase-init own the same
settings means two code paths fighting (hence <code>customizeGuestOs: false</code>).</p>
<p>Driving the guest from outside means vRO calling into the VM for two hours.
That needs guest credentials held centrally, and it keeps a management path
open for the whole build. ABX (Aria&rsquo;s action-based extensibility) can&rsquo;t
reach inside the guest at all.</p>
<p>The platform&rsquo;s job is what needs platform credentials: the hostname from
AD and the placement facts from vCenter. The guest does the guest, under
the domain&rsquo;s rules, from the first reboot after the join.</p>
<p>Even if the GPO were relaxed tomorrow, don&rsquo;t simplify the wrappers away.
The ceiling and the startup trigger are worth having on their own.</p>
<h2 id="the-hand-off-03-init-puller">The hand-off: <code>03-init-puller</code></h2>
<p>This is where the code baked into the template stops, and the centrally
managed engine starts:</p>
<ol>
<li>Create <code>Flags\ Logs\ Data\ Scripts\</code> under the log folder.</li>
<li>Write <strong><code>data-payload.json</code></strong>, the one document everything after this
reads. It holds the share path and accounts, the image, the project,
deployment and requester, the installer folder and file pairs, the tags
and the placement facts.</li>
<li><strong>Encrypt the two share passwords</strong> with AES, using a key derived from
the machine&rsquo;s BIOS UUID. Copied off the box, the payload is useless.</li>
<li>Generate a runner, and run it through a one-shot task as local admin.
It maps the share read-only, copies <code>04-stage-payloads</code>,
<code>05-build-master</code> and <code>06-validate-build</code> locally, unmaps, and launches
staging.</li>
<li>Write the flag and exit 1003. Reboot 2. cloudbase-init&rsquo;s job is done,
though nobody has mentioned the uninstall to it yet.</li>
</ol>
<p><code>04-stage-payloads</code> copies every installer to local disk. Installers never
run across SMB, so they&rsquo;re immune to network blips and file locks
mid-install. It also registers the <strong><code>Build-Master</code> startup task</strong>. From
here on, every boot runs <code>05-build-master.ps1</code> until the build is complete.</p>
<p>What the requester actually sees is a short form. Leaving out the
site-specific enum values, the inputs are:</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">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">location</span><span class="p">:</span><span class="w">        </span><span class="c"># site code -&gt; hostname prefix</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">classification</span><span class="p">:</span><span class="w">  </span><span class="c"># security zone -&gt; hostname prefix, OU</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">environment</span><span class="p">:</span><span class="w">     </span><span class="c"># prod / pre-prod / test -&gt; hostname prefix, tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">     </span><span class="c"># application code -&gt; hostname prefix, folder</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w">           </span><span class="c"># Windows2025 (the template is image-versioned)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">flavor</span><span class="p">:</span><span class="w">          </span><span class="c"># Small / Medium / Large -&gt; vCPU + RAM</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">count</span><span class="p">:</span><span class="w">           </span><span class="c"># number of identical machines</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">bootDiskSizeGB</span><span class="p">:</span><span class="w">  </span><span class="c"># C: (extended in-guest by 01-config-disks)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">primaryNetwork</span><span class="p">:</span><span class="w">  </span><span class="c"># required</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">network2..4</span><span class="p">:</span><span class="w">     </span><span class="c"># optional; each becomes a static NIC</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">additionalDisks</span><span class="p">:</span><span class="w"> </span><span class="c"># [{number, name, letter, sizeGB}] -&gt; D:, L:, ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tags</span><span class="p">:</span><span class="w">            </span><span class="c"># free-form key/value -&gt; written to disk, shown in the report</span><span class="w">
</span></span></span></code></pre></div><p>Each of those shapes the hostname, lands in <code>guestinfo</code>, or is written to
disk for the build engine to read. Nothing is entered twice.</p>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>For an organisation, this pipeline turns a Windows server from something a
person builds into something the platform <em>delivers</em>. A request goes into a
catalog, and a domain-joined server with its agents comes out. The security
team&rsquo;s controls (naming, join, hardening, monitoring agents) apply every
time, because they&rsquo;re in the pipeline, not in a checklist.</p>
<p>The separation of concerns is what keeps it maintainable. The platform
holds the credentials and the guest does the work. A change to the
software stack is a script update on a share, not a template re-release.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>Split by <strong>who holds the credential</strong>. The platform queries AD and
vCenter; the guest never does. <code>guestinfo</code> is the one-way bridge.</li>
<li><strong>Disable vCenter customization</strong> when cloudbase-init owns the guest.
One owner.</li>
<li><strong>Pull the engine</strong> from a share. Embed only what must run before the
network exists.</li>
<li>Flag files, transcripts and exit 1003 make first-boot scripts idempotent
and reboot-safe.</li>
<li>Anything after the domain join runs in a <strong>scheduled task under an
explicit identity</strong>: for policy, for the execution ceiling, and for the
startup trigger.</li>
<li>Never reuse hostname gaps.</li>
</ul>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/aria/aria-automation/8-18/assembler-on-prem-using-and-managing-master-map-8-18/maphead-designing-your-deployments/initialize-general/initialize-windows-general/initialize-cloudbase-init.html">Cloudbase-Init commands for Windows in Automation Assembler</a>: a <code>cloudConfig</code> section of Cloudbase-Init commands in the cloud template</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/aria/aria-automation/8-18/assembler-on-prem-using-and-managing-master-map-8-18/maphead-designing-your-deployments/initialize-general/initialize-windows-general/initialize-windows-image-vsphere.html">Windows Automation Assembler image for vSphere</a>: the Windows template with Cloudbase-Init installed to run as LocalSystem</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/aria/aria-automation/8-18/assembler-on-prem-using-and-managing-master-map-8-18/maphead-designing-your-deployments/initialize-general/initialize-vsphere-static-ips.html">vSphere static IP addresses in Automation Assembler</a>: why <code>customizeGuestOs</code> must be <code>false</code> when the <code>cloudConfig</code> sets the network</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/aria/aria-automation/8-18/assembler-on-prem-using-and-managing-master-map-8-18/maphead-designing-your-deployments/maphead-extensibility-in-cloud-assembly/learn-more-about-extensibilty-subscriptions/event-topics-provided-with-cloud-assembly.html">Event topics provided with Automation Assembler</a>: Compute allocation, where resource names can still change, and Compute post provision</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/aria/aria-automation/8-18/assembler-on-prem-using-and-managing-master-map-8-18/maphead-designing-your-deployments/maphead-extensibility-in-cloud-assembly/extensibility-workflow-subscriptions/how-do-i-modify-virtual-machine-properties-using-a-vro-workflow-subscription.html">How do I modify virtual machine properties using a Automation Orchestrator Client workflow subscription</a>: an Orchestrator workflow subscribed to Compute allocation to set the VM name</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vsphere/tools/12-5-0/vmware-tools-administration-12-5-0/configuring-vmware-tools-components/using-vmware-tools-configuration-utility/view-virtual-machine-status-information/query-information-using-guestinfo-variable.html">Query Information using GuestInfo Variable</a>: reading <code>guestinfo</code> variables from inside the guest with VMware Tools</li>
</ul>
<p><em>Part 2: <a href="/posts/windows-2025-aria-part-2/">the state machine that survives four reboots</a>.</em></p>
<hr>
<p><em>Lab write-up of a production pattern; customer specifics removed. Opinions
my own.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Seven demo apps, one request: deploying a showcase stack via VCF Automation</title>
      <link>https://thenestedlab.com/posts/demo-apps-via-vcfa/</link>
      <pubDate>Wed, 16 Sep 2026 08:40:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/demo-apps-via-vcfa/</guid>
      <description>Seven demo apps, Pac-Man and KubeDoom among them, on a VKS cluster that VCF Automation built in a tenant VPC, each with its own NSX virtual IP. The proper tenanted path, what you get for free, and the traps.</description>
      <content:encoded><![CDATA[<p>Every platform needs a demo stack: something that looks alive on a
projector and quietly exercises every layer underneath. Ours is seven apps
on a vSphere Kubernetes Service (VKS) cluster that VCF Automation
provisioned, in a tenant VPC, each behind its own load-balancer VIP.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="n">kubedoom</span>       <span class="mf">192.168</span><span class="o">.</span><span class="mf">144.20</span><span class="p">:</span><span class="mi">5900</span>   <span class="p">(</span><span class="n">VNC</span> <span class="err">—</span> <span class="n">yes</span><span class="p">,</span> <span class="n">it</span> <span class="n">kills</span> <span class="n">pods</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">kubeinvaders</span>   <span class="mf">192.168</span><span class="o">.</span><span class="mf">144.21</span>
</span></span><span class="line"><span class="cl"><span class="n">kube</span><span class="o">-</span><span class="n">ops</span><span class="o">-</span><span class="n">view</span>  <span class="mf">192.168</span><span class="o">.</span><span class="mf">144.22</span>
</span></span><span class="line"><span class="cl"><span class="n">pacman</span>         <span class="mf">192.168</span><span class="o">.</span><span class="mf">144.23</span>        <span class="p">(</span><span class="o">+</span> <span class="n">MongoDB</span> <span class="n">on</span> <span class="n">a</span> <span class="n">PVC</span> <span class="err">—</span> <span class="n">persistent</span> <span class="n">high</span> <span class="n">scores</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">podinfo</span>        <span class="mf">192.168</span><span class="o">.</span><span class="mf">144.24</span>
</span></span><span class="line"><span class="cl"><span class="n">goldpinger</span>     <span class="mf">192.168</span><span class="o">.</span><span class="mf">144.25</span>        <span class="p">(</span><span class="n">DaemonSet</span> <span class="n">incl</span><span class="o">.</span> <span class="n">control</span> <span class="n">plane</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">prometheus</span>     <span class="p">(</span><span class="ow">in</span><span class="o">-</span><span class="n">cluster</span><span class="p">:</span> <span class="n">KSM</span> <span class="o">+</span> <span class="n">node</span><span class="o">-</span><span class="n">exporter</span><span class="p">,</span> <span class="mi">11</span><span class="o">/</span><span class="mi">11</span> <span class="n">targets</span> <span class="n">up</span><span class="p">)</span>
</span></span></code></pre></div><p><img alt="Three of the seven live on their VIPs — KubeInvaders, kube-ops-view, Pac-Man — and the whole set as VCF Operations sees it" loading="lazy" src="/images/demo-apps-grid.jpg">
<em>Captured from the VIPs the platform handed out. KubeDoom is VNC-only and podinfo is an API, so they sit this one out; the Ops topology tile shows all seven by name.</em></p>
<h2 id="the-path-that-matters-tenanted-not-shortcut">The path that matters: tenanted, not shortcut</h2>
<p>I deployed the first version of this stack <em>against the supervisor</em>: an
admin kubeconfig, a vSphere namespace, <code>kubectl apply</code>. It worked, and it
was wrong, for the reason the <a href="/posts/vks-kubectl-vs-vcfa-all-apps/">previous
post</a> spells out. Nothing about it
was <em>provided</em> to anyone.</p>
<p>So I tore it down: seven apps, cluster, VPC and VIPs, gone in about seven
minutes. Demolition, as ever, was the quick part. Then I rebuilt it the
proper way:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">dev-01 org → default-project → SupervisorNamespace (class large, region f06, VPC default-f06)
</span></span><span class="line"><span class="cl">          → VKS cluster vks-demo01 → seven apps
</span></span></code></pre></div><p>Everything went in <strong>through VCF Automation</strong>: the Cloud Consumption
Interface (CCI) API for the namespace and cluster, then the apps through
the cluster&rsquo;s own kubeconfig. Two auth facts are worth writing down; each
cost me a cycle:</p>
<ul>
<li>A <strong>provider</strong> service account reaches the cloud API only. CCI
(<code>/cci/kubernetes/apis/...</code>) rejects provider tokens with a 401. It needs an
<strong>org-scoped</strong> service account.</li>
<li>Device-flow login uses the service account&rsquo;s own UUID as the <code>client_id</code>
(not its software ID), against the tenant endpoint
<code>/oauth/tenant/&lt;org&gt;/device_authorization</code>.</li>
</ul>
<p><img alt="VCF Operations topology: the cluster with its apps named" loading="lazy" src="/images/ui/u9-ops-vks-topology.jpg"></p>
<h2 id="what-the-platform-does-for-free">What the platform does for free</h2>
<p>Each app is an ordinary Deployment plus a <code>Service</code> of type <code>LoadBalancer</code>.
The supervisor turns each service into an NSX VPC load-balancer virtual
server, and hands back a VIP from the org&rsquo;s external block. No ingress
controller, no MetalLB, no port-forwarding. Seven services, seven VIPs, done.</p>
<p>Pac-Man&rsquo;s MongoDB asks for a persistent volume and gets one on vSAN,
through the CSI driver the supervisor already installed. The high scores
are on enterprise storage. Priorities.</p>
<p>Goldpinger runs as a DaemonSet on every node, the control plane included,
and draws the node-to-node mesh live.</p>
<p>That&rsquo;s three platform services (load balancing, storage and networking)
exercised by apps that know nothing about VCF.</p>
<h2 id="the-traps">The traps</h2>
<p><strong>The supervisor refuses cluster-scoped RBAC, even to admin.</strong> Demo apps
that need ClusterRoles (kube-ops-view, Goldpinger, KubeDoom) <em>must</em> live on
a guest cluster. You can&rsquo;t run them as vSphere Pods on the supervisor.</p>
<p><strong>PodSecurity <code>restricted</code> is the VKS 1.35 default.</strong> Half the demo set
runs as root. The symptom: the Deployment shows <code>0 UP-TO-DATE</code>, the
ReplicaSet exists, zero pods, and the events say <code>FailedCreate</code>. The fix:
label the namespace <code>pod-security.kubernetes.io/enforce=privileged</code>. Then
say so in the demo, because it&rsquo;s a teaching moment.</p>
<p><strong>node-exporter without <code>hostNetwork</code>.</strong> The VPC fabric blocks scrapes from
a pod to a node IP, so the stock DaemonSet&rsquo;s <code>hostNetwork: true</code> doesn&rsquo;t
help. Run it as a normal pod and let Prometheus scrape it in-cluster.</p>
<p><strong>Docker Hub is flaky from behind a proxy.</strong> TLS handshake timeouts put
pods into kubelet&rsquo;s image-pull backoff, where they sit and sulk. Deleting
the stuck pods gets round the backoff. A Harbor proxy-cache project fixes
it properly.</p>
<p><strong>Pod CIDR shadowing.</strong> The stock <code>192.168.0.0/16</code> pod range hid the
org&rsquo;s <code>192.168.144.0/21</code> external block <em>from inside the cluster</em>, so apps
couldn&rsquo;t reach their neighbours&rsquo; VIPs. The pod CIDR is now <code>172.16.0.0/16</code>.</p>
<h2 id="automation-notes">Automation notes</h2>
<p>The whole stack is one checkbox on the lab&rsquo;s catalog item that deploys a
supervisor. <code>installDemoApps</code> creates the cluster, with the newest
compatible Kubernetes release and the newest built-in ClusterClass, both
auto-detected. Then it applies the seven apps and reports their URLs in the
deployment summary.</p>
<p>On a fresh environment, an integrated run takes 16.7 minutes to
<code>CREATE_SUCCESSFUL</code>. An immediate re-run takes 3.3 minutes, every step
idempotent. That&rsquo;s the number I actually care about: it makes a broken demo
a re-run, not a rebuild. Demos have an uncanny sense of when they&rsquo;re being
watched.</p>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>A demo stack sounds like a toy. To be fair, one of the apps is Pac-Man.
But it&rsquo;s actually the fastest way to make a platform <em>legible</em> to people who
don&rsquo;t read YAML. A customer watches a request become a cluster, watches
seven services get their own addresses, then opens one and plays it.</p>
<p>Everything underneath gets exercised (self-service Kubernetes, load
balancing, persistent storage, isolation), and a non-technical stakeholder
can see it working. The same stack is what we put in front of a new team on
day one. And the same idempotent deploy is what makes it safe to
demonstrate live: if it breaks on stage, it re-runs in three minutes.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>Demo apps that need cluster-scoped RBAC <strong>must</strong> run on a guest cluster.
The supervisor won&rsquo;t grant it, even to admin.</li>
<li>Build the stack through the <strong>tenanted path</strong>: org service account, then
CCI, then namespace, then cluster, then apps. Same apps, but now they&rsquo;re
provided, quota&rsquo;d and visible in Ops.</li>
<li>VKS 1.35 has <code>restricted</code> PodSecurity by default. Label the namespace and
say why.</li>
<li>One <code>LoadBalancer</code> per app means one NSX VIP per app. No ingress needed
for a demo.</li>
<li>Pick a pod CIDR that doesn&rsquo;t overlap the VPC external block.</li>
<li>Make the deploy idempotent. A demo that re-runs in 3 minutes is one you
can afford to break on stage.</li>
</ul>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/administration-sdks-cli-and-tools/about-the-vcf-automation-api/tenant-portal/creating-and-managing-namespaces.html">Creating and Managing Namespaces</a>: a <code>SupervisorNamespace</code> with a class, region and VPC, through the All Apps API</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/administering-users-and-groups-in-vcf-automation-for-all-apps/create-a-service-account-in-your-vcf-automation-organization.html">Create a Service Account in Your VCF Automation Organization</a>: organization service accounts and their device-bound API tokens</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-consumption/latest/managing-vsphere-kuberenetes-service-clusters-and-workloads/deploying-workloads-on-tkg-service-clusters/pod-deployment-with-load-balancer-service.html">Pod Deployment with Load Balancer Service</a>: a <code>Service</code> of type <code>LoadBalancer</code> on a VKS cluster and its external IP</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-consumption/latest/managing-vsphere-kuberenetes-service-clusters-and-workloads/deploying-workloads-on-tkg-service-clusters/storage-concepts-for-tkg-service-clusters.html">Storage for VKS Clusters</a>: persistent volumes from the storage classes assigned to the namespace</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-consumption/latest/managing-vsphere-kuberenetes-service-clusters-and-workloads/managing-security-for-tkg-service-clusters/configure-psa-for-tkr-1-25-and-later.html">Configure PSA for VKr 1.25 and Later</a>: <code>restricted</code> enforced by default from VKr 1.26, and the namespace label that relaxes it</li>
</ul>
<p><em>Previously: <a href="/posts/vks-kubectl-vs-vcfa-all-apps/">one VKS cluster, two ways</a>.
The Pac-Man instance here is the one from <a href="/posts/whats-a-vpc-with-pacman/">What&rsquo;s a VPC?</a>.</em></p>
<hr>
<p><em>Lab environment; opinions my own.</em></p>
]]></content:encoded>
    </item>
    <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, nested ESXi hosts and all, as one VCF Automation blueprint in the catalog. How the blueprint is built, the order 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>.</p>
<p>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. Barely time to put the kettle on.</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 run through an IaaS
(infrastructure as a service) engine that drives vCenter. <strong>All Apps</strong> is the
supervisor-native path. Its blueprint composes Kubernetes objects (a
Supervisor Namespace, VM Service VMs, NSX subnets, vSphere Kubernetes Service
clusters), and the vSphere Supervisor&rsquo;s controllers reconcile them.</p>
<p>A blueprint is <code>formatVersion: 2</code>, and its resources are
<code>CCI.Supervisor.Namespace</code> and <code>CCI.Supervisor.Resource</code>. The second is
literally &ldquo;here&rsquo;s a manifest, apply it in that namespace.&rdquo; So the blueprint
is 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. Our libraries were plain vCenter libraries, and a namespace created by
VCF Automation got <strong>none of them</strong>. No libraries meant no
<code>VirtualMachineImage</code>s, so nothing could be deployed. An empty namespace is
very tidy, and of no use to anyone.</p>
<p>Declaring the libraries here attaches them at creation. (The 9.1 docs say a
<a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-projects-in-vcfa/create-a-namespace-class.html">namespace class</a>
is assigned a content library automatically, and provider libraries are
shared with every namespace.)</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>. A fresh VPC realizes its
subnets in the order they were created, 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.</p>
<p>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. Finishing any earlier
would be optimism, not automation.</p>
<h3 id="the-doors--one-vip-per-host">The doors — one VIP per host</h3>
<p>Each host gets a <code>VirtualMachineService</code> of type <code>LoadBalancer</code>, which
selects it by label and publishes 22 and 443. A blueprint <strong>output</strong> then
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 show up in the deployment view, so the requester gets the SSH
command rather than a scavenger hunt.</p>
<p><img alt="Deployment topology after a successful request" loading="lazy" src="/images/ui/u4-deployment-topology.jpg"></p>
<figure class="nl-video">
  <video autoplay loop muted playsinline controls preload="metadata" style="aspect-ratio:1344 / 788" poster="/images/u7-catalog-request-flow-poster.jpg">
    <source src="/images/u7-catalog-request-flow.mp4" type="video/mp4">
  </video>
  <figcaption>The request flow, end to end: request → deployment in progress → complete.</figcaption>
</figure>

<h2 id="what-the-blueprint-cannot-express-yet">What the blueprint cannot express (yet)</h2>
<p>Three cluster-scoped objects 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>, the same in every pod.</li>
<li><code>VPCAttachment</code>: the connectivity profile with the service gateway.
Without it, creating the load balancer fails loudly.</li>
<li><code>LoadBalancer</code>: silently, permanently required before the namespace.</li>
</ol>
<p>VCF Automation 9.1 does have blueprint types for the first two: <code>CCI.VPC</code>,
and <code>CCI.VPC.Configuration</code> with <code>kind: VPCAttachment</code>. One of its
<a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/sample-blueprints-in-vcf-automation-for-all-apps.html">sample blueprints</a>
uses both. There is none for the third.</p>
<p>A later test confirmed it. <code>CCI.VPC.Configuration</code> rejects a <code>LoadBalancer</code>
kind, and a VPC created that way comes up with load balancing off. Its
namespace&rsquo;s VIPs would sit pending, waiting for a load balancer that isn&rsquo;t
coming.</p>
<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 the load balancer to become blueprintable
too. 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>The order is blueprint, then <code>BlueprintVersion</code>, then release. Validation
happens at <em>version</em> time, not at create time. The result lives in
<code>status.validationMessages</code> rather than the HTTP code, so a 200 with
<code>ContentValid: False</code> is a thing: the HTTP equivalent of &ldquo;yes, but no&rdquo;.</p>
<p>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 gap between &ldquo;we
can build you an environment&rdquo; and &ldquo;request one from the catalog&rdquo; is the gap
between days and minutes. It&rsquo;s also the gap 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
shape of environment: 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>Use <code>generateName</code>, not <code>name</code>, for the namespace, and <code>contentSources</code> to
attach libraries at creation. Keep <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 and LoadBalancer are <strong>prerequisites outside the
blueprint</strong>, in that order, before every request.</li>
<li>One published version per blueprint. Validation results live in <code>status</code>,
not in the HTTP response.</li>
</ul>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/sample-blueprints-in-vcf-automation-for-all-apps.html">Sample Blueprints in VCF Automation</a>: <code>CCI.Supervisor.Namespace</code> and <code>CCI.Supervisor.Resource</code> examples, with <code>generateName</code> and <code>context</code>.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/bindings-and-dependencies.html">Creating bindings and dependencies between resources in blueprints in VCF Automation</a>: <code>dependsOn</code> and property bindings, which set the build order.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/specifying-formatversion-in-your-blueprints.html">Specifying formatVersion in Blueprints in VCF Automation</a>: what <code>formatVersion: 2</code> adds, outputs included.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/blueprint-versioning.html">Versioning Blueprints in VCF Automation</a>: blueprint versions, and releasing one to the catalog.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-service-administration-and-development/9-1/provision-and-manage-virtual-machines/deploying-and-managing-virtual-machines-in-vsphere-iaas-control-plane/creating-and-managing-content-libraries-for-stand-alone-vms-in-iaas-platform.html">Creating and Managing Content Libraries for Stand-Alone VMs in vSphere Supervisor</a>: VM content libraries and the namespaces they are associated with.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-consumption/latest/vm-service/deploy-vms-with-configurable-ovf-properties-vsphere-iaas-control-plane.html">Deploy VMs with Configurable OVF Properties in vSphere Supervisor</a>: OVF properties set through the VM Service&rsquo;s vAppConfig transport.</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>Seven ways the nested-ESXi pod blueprint failed validation before it worked, from ${input} inside flow mappings to an 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; failures, 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. (That&rsquo;s 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>. The
Cloud Consumption Interface (CCI) API rejects <code>metadata.name</code>. The platform
appends a random suffix, so <code>pod-a-</code> becomes <code>pod-a-dgf5p</code>, which really
rolls off the tongue. 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>My 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 is at least 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 you get: no <code>VirtualMachineImage</code>
found. Our libraries were plain vCenter libraries, and a namespace created by
VCF Automation attached <strong>none</strong> of them. (The 9.1 docs say a
<a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-projects-in-vcfa/create-a-namespace-class.html">namespace class</a>
is assigned a content library automatically, and that provider libraries are
shared with every namespace.) 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 at once, but 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.</p>
<p>In a blueprint, make 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. A 200 here means &ldquo;I heard you&rdquo;, not &ldquo;I agree&rdquo;. 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.</p>
<p>In practice, 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 to exist before the request: VPC, VPCAttachment and
LoadBalancer, <a href="/posts/the-lb-that-must-exist-first/">in that order</a>. VCF
Automation 9.1 has blueprint types for the first two: <code>CCI.VPC</code>, and
<code>CCI.VPC.Configuration</code> with <code>kind: VPCAttachment</code> (see its
<a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/sample-blueprints-in-vcf-automation-for-all-apps.html">sample blueprints</a>).
But it has none for the load balancer, and a VPC created that way comes up
with load balancing off.</p>
<p>So we create all three before the request. 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. Most
of these seven are not documented, and all of them stall a first project by
days if you meet them cold.</p>
<p>The value of a delivery partner who has already built on the platform isn&rsquo;t
the YAML. It&rsquo;s that the first blueprint a customer publishes goes live on
day one instead of week two. And it&rsquo;s 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 for vCenter libraries, 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>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/sample-blueprints-in-vcf-automation-for-all-apps.html">Sample Blueprints in VCF Automation</a>: a new namespace with <code>generateName</code>, and resources placed in it with <code>context</code>.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-projects-in-vcfa/create-a-namespace-class.html">Create a Namespace Class in VCF Automation</a>: VM and storage classes, per-zone limits, and the content libraries a namespace gets.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-service-administration-and-development/9-1/provision-and-manage-virtual-machines/deploying-and-managing-virtual-machines-in-vsphere-iaas-control-plane/creating-and-managing-content-libraries-for-stand-alone-vms-in-iaas-platform.html">Creating and Managing Content Libraries for Stand-Alone VMs in vSphere Supervisor</a>: associating VM content libraries with a namespace.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/bindings-and-dependencies.html">Creating bindings and dependencies between resources in blueprints in VCF Automation</a>: <code>dependsOn</code>, the explicit build order.</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/blueprint-versioning.html">Versioning Blueprints in VCF Automation</a>: versions, and releasing one to the catalog.</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>
    <item>
      <title>VM Apps vs All Apps: a field comparison of VCF Automation&#39;s two provisioning models</title>
      <link>https://thenestedlab.com/posts/vm-apps-vs-all-apps/</link>
      <pubDate>Wed, 16 Sep 2026 07:00:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/vm-apps-vs-all-apps/</guid>
      <description>VCF Automation 9.1 has two provisioning models, as two kinds of organisation. I ran the same use cases through both, from Windows VMs to nested ESXi. The honest scorecard, and how each case is done.</description>
      <content:encoded><![CDATA[<p>VCF Automation 9.1 has two ways to build things, side by side, as two types
of organisation. If you&rsquo;ve come from Aria Automation, you&rsquo;ll recognise one
of them immediately. The other looks like Kubernetes because it <em>is</em>
Kubernetes.</p>
<p>Choosing between them isn&rsquo;t a matter of taste. They have genuinely
different shapes, and some use cases are natural in one and awkward in the
other.</p>
<p>I&rsquo;ve now pushed the same list of requirements through both on a live VCF
9.1 lab. This is the comparison I wish I&rsquo;d had at the start. Hindsight, as
usual, turned up late.</p>
<h2 id="the-two-shapes">The two shapes</h2>
<p><img alt="VM Apps: Org, Project, Cloud Zone, Cloud Account, profiles, Cloud Template, then the IaaS engine drives vCenter, imperatively, with state in the IaaS database. All Apps: Org, CCI Project, Region, VPC, Supervisor Namespace, blueprint, then Supervisor controllers reconcile, declaratively, with state in etcd" loading="lazy" src="/images/diagrams/vm-apps-vs-all-apps.svg">
<em>The same request, brokered on the left and declared on the right. The text versions below carry the detail.</em></p>
<p><strong>VM Apps</strong>, the classic Aria Automation model:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Org ─ Project ─ Cloud Zone(s)
</span></span><span class="line"><span class="cl">                   │
</span></span><span class="line"><span class="cl">        Cloud Account (vCenter / NSX)
</span></span><span class="line"><span class="cl">                   │
</span></span><span class="line"><span class="cl">   flavor · image · network · storage profiles
</span></span><span class="line"><span class="cl">                   │
</span></span><span class="line"><span class="cl">   Cloud Template (formatVersion 1) ──▶ IaaS engine ──▶ vCenter API
</span></span><span class="line"><span class="cl">        Cloud.vSphere.Machine, Cloud.NSX.Network, customization spec
</span></span></code></pre></div><p>Provisioning is <em>imperative through a broker</em>. The IaaS engine holds the
vCenter session. The tenant references abstractions (flavors, image
mappings) that resolve at request time. State lives in the IaaS database.</p>
<p><strong>All Apps</strong>, supervisor-native, through the Cloud Consumption Interface:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Org ─ CCI Project ─ Region
</span></span><span class="line"><span class="cl">          │
</span></span><span class="line"><span class="cl">   VPC (tenant-created; overlapping privateIPs allowed)
</span></span><span class="line"><span class="cl">          │   + VPCAttachment   + LoadBalancer (before the namespace!)
</span></span><span class="line"><span class="cl">   Supervisor Namespace (spec.vpcName pins it)
</span></span><span class="line"><span class="cl">          │
</span></span><span class="line"><span class="cl">   Kubernetes objects reconciled by the Supervisor:
</span></span><span class="line"><span class="cl">     VirtualMachine / VirtualMachineService / Subnet / BindingMap / VKS Cluster
</span></span><span class="line"><span class="cl">          │
</span></span><span class="line"><span class="cl">   Blueprint (formatVersion 2, CCI.Supervisor.*) → BlueprintVersion → Catalog
</span></span></code></pre></div><p>Provisioning is <em>declarative</em>. The blueprint states the desired objects,
and the supervisor&rsquo;s controllers make reality match them and keep it that
way. State lives in etcd. Networking is NSX VPC-native.</p>
<h2 id="scorecard-by-use-case">Scorecard by use case</h2>
<p>Every row below was actually built, not read about. It&rsquo;s a slow way to fill
in a table, but an honest one.</p>
<table>
	<thead>
			<tr>
					<th>Use case</th>
					<th>VM Apps</th>
					<th>All Apps</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Linux VM + software</td>
					<td><code>Cloud.vSphere.Machine</code> + cloudConfig</td>
					<td>VM Service VM + cloud-init (typed field)</td>
			</tr>
			<tr>
					<td>Windows VM</td>
					<td>customization spec + software components</td>
					<td><code>bootstrap.sysprep</code>; ISO attachable declaratively</td>
			</tr>
			<tr>
					<td>ISO / CD-ROM attach</td>
					<td>day-2 edit in vCenter</td>
					<td><code>hardware.cdrom</code> in the manifest</td>
			</tr>
			<tr>
					<td>Multi-NIC</td>
					<td>NICs across network profiles</td>
					<td>multiple <code>interfaces</code> on VPC subnets; failover verified</td>
			</tr>
			<tr>
					<td>Self-service catalog</td>
					<td>template released to catalog</td>
					<td>blueprint → version → publish (one live version)</td>
			</tr>
			<tr>
					<td>Load-balanced access</td>
					<td>NSX LB via network profile</td>
					<td><code>VirtualMachineService</code> → VPC LB VIP</td>
			</tr>
			<tr>
					<td>Kubernetes clusters</td>
					<td>separate TKG integration</td>
					<td>native <code>Cluster</code> object in the namespace</td>
			</tr>
			<tr>
					<td><strong>Identical isolated environments</strong></td>
					<td>hard: on-demand NAT nets, unique addressing usually forced</td>
					<td><strong>natural</strong>: VPC per pod, overlapping CIDRs, zero route between</td>
			</tr>
			<tr>
					<td><strong>Nested ESXi / VLAN networks</strong></td>
					<td>trunk portgroups on the physical vDS — a fabric change</td>
					<td><strong>trunk subnet + binding maps</strong> — no fabric change</td>
			</tr>
			<tr>
					<td>Shared infra (WSUS, repos)</td>
					<td>shared segment routed everywhere</td>
					<td>one <code>PrivateTGW</code> subnet, one-way reachability</td>
			</tr>
			<tr>
					<td>Extensibility</td>
					<td>vRO, ABX, event broker (rich)</td>
					<td>controllers, GitOps, kubectl (thinner day-2 today)</td>
			</tr>
			<tr>
					<td>Deep per-device vSphere tuning</td>
					<td>anything vCenter can do</td>
					<td>what the VM Service API models</td>
			</tr>
	</tbody>
</table>
<p>The two bold rows are the ones that decided it for me. Both are
<a href="/posts/three-datacenters-one-ip-plan/">documented</a> <a href="/posts/nested-esxi-nsx-vpc/">in this
series</a>. Both are genuinely hard in VM Apps,
and simply the default in All Apps.</p>
<h2 id="where-vm-apps-still-wins">Where VM Apps still wins</h2>
<p>Be fair to the incumbent:</p>
<ul>
<li><strong>Years of content.</strong> vRO workflows, ABX actions, template libraries and
a mature day-2 action framework carry over unchanged. If you have an
estate of them, that&rsquo;s real value you&rsquo;d be throwing away.</li>
<li><strong>Deep vSphere reach.</strong> Anything vCenter can do to a VM (RDMs, per-device
tuning, exotic customization), the IaaS engine can do, because it drives
vCenter directly.</li>
<li><strong>Familiar network model.</strong> Segments, portgroups, on-demand routed/NAT
networks from a network profile. No new mental model.</li>
<li><strong>Multi-cloud lineage.</strong> The same template idiom stretched to other
endpoints.</li>
</ul>
<p>The <a href="/series/the-windows-build-pipeline/">Windows Server 2025 pipeline</a>
elsewhere on this blog is a VM Apps build, and it&rsquo;s a good one. It has Event
Broker hooks for hostname allocation and placement metadata, cloudbase-init
staging, and a reboot-safe state machine in the guest. Nothing about it
needs rewriting, which, for a pipeline, is high praise.</p>
<h2 id="where-all-apps-wins-and-why-its-structural">Where All Apps wins, and why it&rsquo;s structural</h2>
<ul>
<li><strong>Declarative and self-healing.</strong> Desired state lives in etcd, and
controllers reconcile to it. There&rsquo;s no second database to drift from
vCenter.</li>
<li><strong>Structural multi-tenancy.</strong> A VPC per tenant is a hard NSX boundary,
not an administrative one. Overlapping CIDRs are <em>allowed</em>, so
cookie-cutter environments deploy side by side.</li>
<li><strong>VMs and Kubernetes are one model.</strong> The same blueprint composes a VKS
cluster, VMs, secrets and networking. It&rsquo;s GitOps-able with ordinary
tools.</li>
<li><strong>VPC networking is first-class.</strong> Trunk subnets, binding maps,
<code>PrivateTGW</code>, a per-VPC gateway firewall: none of it has a VM Apps
equivalent.</li>
<li><strong>Modern bootstrap.</strong> cloud-init and sysprep as typed API fields.</li>
</ul>
<h2 id="where-all-apps-hurts-today-all-observed-live">Where All Apps hurts today (all observed live)</h2>
<ul>
<li><strong>Ordering matters, and the errors are opaque.</strong> <a href="/posts/the-lb-that-must-exist-first/">The LB must exist
before the namespace</a>. New
namespaces reject VMs until images sync. Blueprint validation has
<a href="/posts/cci-blueprint-gotchas/">seven sharp edges</a>, and I found every
one of them by walking into it.</li>
<li><strong>The tenancy layer is only partly blueprintable.</strong> VPC and VPCAttachment
have blueprint types (<code>CCI.VPC</code>, <code>CCI.VPC.Configuration</code>). But there&rsquo;s no
LoadBalancer kind, and a blueprint-made VPC comes up with load balancing
off. So all three go through the VPC API: scripted, not catalogued.</li>
<li><strong>Two endpoints.</strong> The CCI proxy serves tenancy objects, and workload
manifests go to the supervisor. You&rsquo;ll hold two kubeconfigs.</li>
<li><strong>Day-2 is thinner.</strong> There&rsquo;s no event broker. Extensibility means
controllers and GitOps, which is fine if that&rsquo;s your team and a gap if it
isn&rsquo;t.</li>
</ul>
<h2 id="recommendation">Recommendation</h2>
<p>Default to <strong>All Apps</strong> for new build-outs. Every use case on the list is
natural in the VPC model, including the two that are genuinely hard in VM
Apps. And the whole estate is version-controlled YAML.</p>
<p>Keep <strong>VM Apps</strong> as the compatibility surface for existing vRA content, and
for the rare thing that needs direct vCenter device manipulation. They
coexist as organisations of different types on one platform. So migration
is incremental, and nobody has to rewrite a working pipeline on a deadline.</p>
<p>And whichever you pick, <strong>codify the ordering rules</strong> into the scripts
that provision tenancy. The sharp edges stay wrapped up in there, and the
people requesting catalog items never meet them.</p>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>Most customers arriving at VCF 9 have an estate of Aria Automation content,
and a question: rewrite, coexist, or migrate? The answer above is the one
we take into design workshops.</p>
<p>In practice it starts with an assessment:</p>
<ul>
<li>which existing templates and workflows still earn their keep;</li>
<li>which use cases are genuinely better served by the VPC model;</li>
<li>where the boundaries sit.</li>
</ul>
<p>Then comes a coexistence plan that moves workloads across
opportunistically, not on a deadline. What makes that low-risk is the two
architectures sharing one platform, as organisations of different types.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>Two shapes, not two skins: imperative-through-a-broker vs
declarative-reconciled. Pick per use case, not once for the estate.</li>
<li>All Apps is structurally better at <strong>isolation with identical
addressing</strong> and <strong>nested/VLAN networks without fabric changes</strong>.</li>
<li>VM Apps is still the home for <strong>existing vRO/ABX content</strong> and
<strong>deep vCenter device work</strong>.</li>
<li>All Apps&rsquo; pain is <em>ordering</em>, in this order: VPC, attachment, LB,
namespace, subnets, image sync, then workloads. Script it once.</li>
<li>They coexist, so migrate opportunistically.</li>
</ul>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management.html">Organization Management</a>: the two organization types in VCF Automation 9.1, All Apps and VM Apps</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/design/design-library/vcf-automation-deployment-models-9-x/multi-tenancy-design-patterns/shared-tenancy-design.html">Bimodal Consumption Design for VM Apps and All Apps Workloads</a>: VM Apps and All Apps organizations on shared infrastructure, and a phased move between them</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/sample-blueprints-in-vcf-automation-for-all-apps.html">Sample Blueprints in VCF Automation</a>: All Apps blueprints built from <code>CCI.Supervisor.Namespace</code> and <code>CCI.Supervisor.Resource</code></li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-service-administration-and-development/9-1/provision-and-manage-virtual-machines/deploying-and-managing-virtual-machines-in-vsphere-iaas-control-plane.html">Deploying and Managing Virtual Machines in vSphere Supervisor</a>: the VM Service, its declarative API, VM classes and images</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/adding-and-managing-virtual-private-clouds.html">Managing Networking in VCF Automation Organizations</a>: VPCs in an organization, whose private CIDRs need not be unique</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/working-with-the-vcf-automation-catalog/maphead-designing-your-deployments/other-code-examples/vsphere-resource-examples.html">vSphere resource examples in VCF Automation for VM Apps</a>: VM Apps cloud templates built on <code>Cloud.vSphere.Machine</code></li>
</ul>
<hr>
<p><em>Lab environment; opinions my own. Grounded in a VCF 9.1 / vSphere
Supervisor with NSX VPC networking build-out; every row was deployed.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>One VKS cluster, two ways: kubectl vs VCF Automation All Apps</title>
      <link>https://thenestedlab.com/posts/vks-kubectl-vs-vcfa-all-apps/</link>
      <pubDate>Wed, 16 Sep 2026 06:50:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/vks-kubectl-vs-vcfa-all-apps/</guid>
      <description>The same VKS cluster built twice: once with kubectl, once as a VCF Automation request. The Cluster object is identical. Everything around it isn&amp;rsquo;t, and the difference is what you get for free.</description>
      <content:encoded><![CDATA[<p>The <code>Cluster</code> manifest is the same. That&rsquo;s the point of this post, and
also the punchline, so I&rsquo;ve rather given away the ending. <strong>VKS is VKS</strong>
whichever door you walk through.</p>
<p>What differs is everything wrapped around the cluster: who can ask for it,
what limits it, who can see it, and how it shows up in operations tooling.</p>
<p>So: two clusters, one supervisor, one ClusterClass, two paths.</p>
<h2 id="path-a-kubectl-the-way-weve-always-done-it">Path A: kubectl, the way we&rsquo;ve always done it</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">kubectl vsphere login --server &lt;supervisor&gt; --tanzu-kubernetes-cluster-namespace demo
</span></span><span class="line"><span class="cl">kubectl apply -f cluster.yaml
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">cluster.x-k8s.io/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Cluster</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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">clusterNetwork</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="l">pods, services, serviceDomain }  </span><span class="w"> </span><span class="c"># set all three — see below</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">topology</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="l">builtin-generic-v3.6.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">classNamespace</span><span class="p">:</span><span class="w"> </span><span class="l">vmware-system-vks-public         </span><span class="w"> </span><span class="c"># the gotcha</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="l">v1.35.5+vmware.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">controlPlane</span><span class="p">:</span><span class="w"> </span>{<span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">workers</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="l">one node pool, 2 replicas }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">variables</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="l">vmClass, storageClass ]</span><span class="w">
</span></span></span></code></pre></div><p>Fifteen minutes later: a cluster.</p>
<p>It does need a vSphere namespace first, and somebody (an admin) makes that
by hand in the vSphere Client. They attach a content library, assign a VM
class and set the quota. In this lab, that somebody is me.</p>
<h2 id="path-b-the-same-manifest-as-a-catalog-request">Path B: the same manifest, as a catalog request</h2>
<p>In All Apps, the cluster is a <code>CCI.Supervisor.Resource</code> inside a <a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/sample-blueprints-in-vcf-automation-for-all-apps.html">blueprint</a>,
sitting next to a <code>CCI.Supervisor.Namespace</code>:</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">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><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.name}-</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 class="c"># quota comes from the class</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.vpc}</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 class="p">[</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><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cluster</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">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">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.namespace.id}</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 class="l">&lt;the SAME Cluster object as above&gt;</span><span class="w">
</span></span></span></code></pre></div><p>Publish it, and a tenant user requests it from a tile:</p>
<p><img alt="VCFA: VKS cluster list as the tenant sees it" loading="lazy" src="/images/ui/u6a-vks-cluster-list.jpg">
<img alt="VCFA: cluster detail" loading="lazy" src="/images/ui/u6-vks-cluster-detail.jpg"></p>
<p>Fifteen minutes later: a cluster. Byte-identical <code>Cluster</code> object.</p>
<h2 id="what-path-b-adds-for-free">What path B adds for free</h2>
<table>
	<thead>
			<tr>
					<th></th>
					<th>kubectl</th>
					<th>All Apps request</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Who can create</td>
					<td>anyone with a kubeconfig to that namespace</td>
					<td>org users with catalog entitlement (org RBAC)</td>
			</tr>
			<tr>
					<td>Namespace</td>
					<td>pre-created by an admin, by hand</td>
					<td>created by the request, from a <strong>class</strong></td>
			</tr>
			<tr>
					<td>Quota</td>
					<td>set per namespace in the vSphere Client</td>
					<td>inherited from the namespace class (<code>small</code>/<code>medium</code>/<code>large</code>)</td>
			</tr>
			<tr>
					<td>Content library</td>
					<td>admin attaches manually</td>
					<td><code>contentSources</code> on the blueprint</td>
			</tr>
			<tr>
					<td>Networking</td>
					<td>whatever the namespace has</td>
					<td>pinned to a tenant VPC by <code>vpcName</code></td>
			</tr>
			<tr>
					<td>Sizing choices</td>
					<td>edit YAML</td>
					<td>form inputs with enums (worker count, VM class)</td>
			</tr>
			<tr>
					<td>Record</td>
					<td><code>kubectl get cluster</code></td>
					<td>a <strong>deployment</strong> with inputs, owner, history, day-2 actions</td>
			</tr>
			<tr>
					<td>Visibility</td>
					<td>supervisor only</td>
					<td>VCFA inventory <strong>and</strong> VCF Operations</td>
			</tr>
	</tbody>
</table>
<p>That last row is the one operations teams care about:</p>
<p><img alt="VCF Operations: the VKS cluster object with gauges and time series" loading="lazy" src="/images/ui/u8-ops-vks-summary.jpg">
<img alt="VCF Operations: topology view — the cluster and the apps on it, by name" loading="lazy" src="/images/ui/u9-ops-vks-topology.jpg"></p>
<p>The VCFA-deployed cluster appears in the Ops object model: supervisor, then
namespace, then cluster, then nodes, then the workloads running on it. Its
demo apps show up by name in the topology tab.</p>
<p>The kubectl-built cluster is <em>also</em> visible to Ops (it&rsquo;s the same
supervisor, after all). But it has no deployment, no owner, no request
history and no quota lineage. It&rsquo;s a thing that exists, not a thing that
was <em>provided</em>. As far as the paperwork goes, it simply turned up one day.</p>
<h2 id="the-four-gotchas-in-order-of-how-much-time-they-cost">The four gotchas, in order of how much time they cost</h2>
<p>Both paths share the same four traps on VKS 1.35 / VCF 9.1:</p>
<ol>
<li><strong>Our VCFA-created namespace had no content library.</strong> Zero
<code>VirtualMachineImage</code>s means no cluster. Set <code>contentSources</code> in the
blueprint, or attach one by hand. The 9.1 docs say a namespace class
<a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-projects-in-vcfa/create-a-namespace-class.html">gets a content library automatically</a>; our <code>large</code> class had none
assigned.</li>
<li><strong><code>ClusterClass</code> lives in <code>vmware-system-vks-public</code>.</strong> It 404s from
the workload namespace unless the spec sets
<code>topology.classNamespace</code>.</li>
<li><strong>The default Quick Start namespace is 1000M CPU / 1000Mi.</strong> Unusable
for a cluster. Use a real class: <code>small</code> 10000M/10000Mi, <code>medium</code>
20000M, <code>large</code> 40000M.</li>
<li><strong>VKS 1.35 enforces PodSecurity <code>restricted</code> by default.</strong> Demo apps
that run as root get a ReplicaSet and <em>no pods</em>, and the events say
<code>FailedCreate</code>. Label the app namespace
<code>pod-security.kubernetes.io/enforce=privileged</code> (or fix the apps).</li>
</ol>
<p>Plus one that waits until later to bite, as the best ones do. Set
<code>clusterNetwork.serviceDomain</code> explicitly. It&rsquo;s immutable after create, and
without it some add-ons build the service DNS name wrongly (<code>....svc.</code> with
no domain).</p>
<p>And pick a pod CIDR that doesn&rsquo;t shadow your VPC&rsquo;s external range. The
stock <code>192.168.0.0/16</code> hid the org&rsquo;s <code>192.168.144.0/21</code> from inside the
cluster.</p>
<h2 id="so-which-one">So which one?</h2>
<p>Use <strong>kubectl</strong> when you&rsquo;re the platform team proving something on a
supervisor, or debugging.</p>
<p>Use <strong>All Apps</strong> the moment a second person needs a cluster. The request
form is the interface, and the namespace class is the guardrail. The
deployment is the audit trail, and Ops sees a provided service rather than
a stray object.</p>
<p>The cluster&rsquo;s the same either way. The <em>service</em> isn&rsquo;t.</p>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>The business case for the second path is governance without friction.
Development teams get Kubernetes clusters on request. The platform team
gets quotas, ownership, RBAC and a monitoring view of every cluster, for
free.</p>
<p>That&rsquo;s the difference between a managed Kubernetes <em>service</em> and a
collection of clusters nobody can account for. It&rsquo;s typically the gap that
stops organisations offering Kubernetes broadly at all. Everything the
developers touch stays standard Kubernetes; the control lands around it,
not on it.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>The <code>Cluster</code> object is identical across paths. All Apps wraps it; it
doesn&rsquo;t change it.</li>
<li>What All Apps adds: catalog RBAC, class-based quota, library attach,
VPC pinning, deployment history, and a place in the Ops object model.</li>
<li>Four traps on 1.35: no library on our namespace class, <code>classNamespace</code>,
tiny default quota, PodSecurity <code>restricted</code>.</li>
<li>Set <code>serviceDomain</code> and a non-shadowing pod CIDR at create time. Both
are immutable.</li>
</ul>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-consumption/latest/managing-vsphere-kuberenetes-service-clusters-and-workloads/provisioning-tkg-service-clusters/workflow-for-provisioning-tkg-clusters-using-kubectl.html">Workflow for Provisioning VKS Clusters Using kubectl</a>: path A, from Supervisor login to an applied cluster YAML</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-consumption/latest/managing-vsphere-kuberenetes-service-clusters-and-workloads/provisioning-tkg-service-clusters/using-the-cluster-v1beta1-api/using-the-versioned-clusterclass.html">Using the Versioned ClusterClass</a>: the built-in ClusterClass in <code>vmware-system-vks-public</code> and <code>spec.topology.classNamespace</code></li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-blueprints-in-vcf-automation/sample-blueprints-in-vcf-automation-for-all-apps.html">Sample Blueprints in VCF Automation</a>: a <code>Cluster</code> as a <code>CCI.Supervisor.Resource</code> beside a <code>CCI.Supervisor.Namespace</code></li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/managing-projects-in-vcfa/create-a-namespace-class.html">Create a Namespace Class in VCF Automation</a>: CPU and memory limits, VM classes, storage and content libraries per class, and the default small, medium and large</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-consumption/latest/managing-vsphere-kuberenetes-service-clusters-and-workloads/managing-security-for-tkg-service-clusters/configure-psa-for-tkr-1-25-and-later.html">Configure PSA for VKr 1.25 and Later</a>: <code>restricted</code> enforced by default from VKr 1.26, and the namespace label that relaxes it</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-consumption/latest/managing-vsphere-kuberenetes-service-clusters-and-workloads/operating-tkg-service-clusters/monitoring-vks-clusters-using-vcf-operations.html">Monitoring VKS Clusters Using VCF Operations</a>: VKS clusters in VCF Operations, monitored by default on VCF 9.1</li>
</ul>
<p><em>Next in All Apps in Practice: <a href="/series/all-apps-in-practice/">the demo apps that live on this
cluster</a>.</em></p>
<hr>
<p><em>Lab environment; opinions my own.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>One catalog item, one VCF instance: building a lab factory</title>
      <link>https://thenestedlab.com/posts/one-catalog-item-one-vcf-instance/</link>
      <pubDate>Wed, 16 Sep 2026 06:40:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/one-catalog-item-one-vcf-instance/</guid>
      <description>How an interactive PowerShell script grew into a catalog item that builds complete nested VCF 9.1 instances from one request form. The design rules that made it survivable, and the traps behind them.</description>
      <content:encoded><![CDATA[<p>Every nested VCF lab starts the same way: a heroic PowerShell script.
Ours was <code>esxihostdeploy.ps1</code>: ovftool plus PowerCLI, with an interactive
menu asking which environment, which ESX version, which role and how many
hosts.</p>
<p>It worked. It also lived on one person&rsquo;s machine, prompted for credentials,
and knew nothing about anything that comes <em>after</em> the hosts exist. Heroic,
then, but not much of a team player.</p>
<p>This post is about what it became: a set of VCF Automation catalog items
where requesting <strong>one form</strong> produces a complete nested VCF 9.1 instance.
That means ESXi hosts, bringup (vCenter, NSX, SDDC Manager), a vSphere
Supervisor, VCF Automation, Operations and identity. The environment number
is practically the only real input.</p>
<h2 id="the-shape-of-the-factory">The shape of the factory</h2>
<p>Three stages, each a catalog item, plus a wrapper that chains them:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Stage 1  Nested ESX Hosts        VM Apps template + vRO actions
</span></span><span class="line"><span class="cl">         (the old script, reborn declaratively)
</span></span><span class="line"><span class="cl">Stage 2  Deploy VCF 9.1 Instance vRO workflow driving the VCF Installer API
</span></span><span class="line"><span class="cl">         (spec generated, validated, bringup started)
</span></span><span class="line"><span class="cl">Day-N    Supervisor · NSX Edge · VCF Automation · Ops Logs/Networks/RTM ·
</span></span><span class="line"><span class="cl">         Identity (AD)           one catalog item each
</span></span><span class="line"><span class="cl">Wrapper  &#34;Deploy VCF Stack&#34;      one form, checkbox per component
</span></span></code></pre></div><p><img alt="The factory catalog: hosts, bringup, every day-N component, and the wrapper — ten tiles" loading="lazy" src="/images/ui/f1-f00-factory-catalog.jpg"></p>
<p>The wrapper&rsquo;s form has a checkbox per component. Ticking one reveals that
component&rsquo;s tab, with every field already filled in. One lab password feeds
every credential. Tick everything, click request, and go and make a coffee.
Possibly several.</p>
<h2 id="rule-1-derive-everything-from-one-number">Rule 1: derive everything from one number</h2>
<p>Each lab environment is <code>f0X</code>, and <em>everything</em> scales from X by formula:</p>
<table>
	<thead>
			<tr>
					<th>Element</th>
					<th>Pattern</th>
					<th>f03 example</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>2X0n</code></td>
					<td>2307 (edge TEP)</td>
			</tr>
	</tbody>
</table>
<p>The bringup spec is hundreds of lines of JSON, which the VCF Installer wants
and nobody wants to type. A vRO action generates it from a known-good
reference spec plus X. Nobody edits a deployment spec by hand, which means
nobody typo-breaks a bringup at 2am.</p>
<p>When the old script did this, the formulas lived in string concatenation.
Now they live in one action, with the reference spec beside it.</p>
<p>The same philosophy carried into stage 1:</p>
<ul>
<li>the script&rsquo;s &ldquo;next free esxNN index&rdquo; scan became a vRO action bound to the
request form;</li>
<li>its VLAN/IP arithmetic became template expressions;</li>
<li>its <code>--prop:guestinfo.*</code> flags became <code>ovfProperties</code> in the template.</li>
</ul>
<p>Porting a script isn&rsquo;t rewriting it. It&rsquo;s finding the declarative home for
each behaviour.</p>
<h2 id="rule-2-never-wait-for-anything-you-can-watch-instead">Rule 2: never wait for anything you can watch instead</h2>
<p>One constraint shaped the whole design. A VCF Automation request gets about
<strong>two hours</strong> before the platform gives up on it. Our runs died at exactly
two hours with <code>Delegating token is not service token</code>, which is a roundabout
way of saying &ldquo;time&rsquo;s up&rdquo;.</p>
<p>The project&rsquo;s
<a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/getting-started-with-organizations-for-vm-apps-in-vcf-automation/map-head-projects-adding-and-managing-projects/projects-how-do-i-add-a-project-for-my-development-team.html">request timeout</a>
also defaults to two hours, and its Provisioning tab can raise it. We never
tried. A full VCF bringup takes longer than that, and it shouldn&rsquo;t hold a
request open for hours anyway. So the wrapper <em>never waits</em>:</p>
<ul>
<li>Bringup is <strong>fire-and-forget</strong>. The workflow authenticates to the
installer, validates the spec, starts the task, and hands back a
<code>watchTaskId</code>. Re-attach any time to check on it.</li>
<li>Fleet deployments (VCF Automation, Ops for Logs/Networks, metrics) are
server-side tasks. The items submit with <code>waitForCompletion=false</code>.</li>
<li>The supervisor item submits enablement and returns; vCenter carries on.</li>
<li>Only fast, deterministic steps (identity configuration, minutes) run to
completion inside the request.</li>
</ul>
<p><img alt="F06-Mgmt-VCF: the wrapper deployment, Create Successful, 13:12 → 14:05" loading="lazy" src="/images/ui/f3-f00-stack-deployment-success.jpg">
<em>A whole VCF instance as one deployment record. The request finished in under an hour, while the build ran on for twelve.</em></p>
<p>The result: a full-stack kick-off <em>completes</em> as a request in well under an
hour (53 minutes on the run pictured). The actual multi-hour build carries
on as server-side tasks you can watch. The request&rsquo;s job isn&rsquo;t to do the
work. It&rsquo;s to <strong>start the work correctly</strong> and tell you where to watch it.</p>
<p>The corollary: a failed component is recorded, and the remaining components
still run. You fix one thing and re-run one item, not the world.</p>
<h2 id="rule-3-plan-mode-for-infrastructure">Rule 3: plan mode for infrastructure</h2>
<p>Every item in the chain supports <code>validateOnly</code>, and the wrapper cascades
it. Tick everything, set validateOnly, and the entire stack is <em>planned</em>
against the live environment with zero changes. Specs are generated,
prerequisites checked, and name and IP collisions caught. A smoke runner
does exactly this on every change to the automation itself.</p>
<p>If you build nothing else into your lab automation, build this. The number
of 2am bringups saved by a five-minute dry run is not small.</p>
<h2 id="the-traps-that-shaped-the-rules">The traps that shaped the rules</h2>
<p>Some of the design above exists because of scars:</p>
<ul>
<li><strong>Hardware validation hates virtual NVMe.</strong> Bringup&rsquo;s hardware
compatibility list (HCL) check will block nested hosts. The spec generator
has to account for it, or you discover it two hours in. Twice, if you&rsquo;re us.</li>
<li><strong>Small disks, surprising layouts.</strong> Nested hosts with 64 GB disks ship
ESX-OSDATA at essentially the whole disk. A post-provision step relocates
scratch, or stage 2 fills the disk with logs.</li>
<li><strong>DNS is a prerequisite, not a step.</strong> The installer&rsquo;s pre-flight wants
every record resolvable before it starts. A one-shot script creates the
per-environment records ahead of the request.</li>
<li><strong>vRO&rsquo;s content-source lag.</strong> A new or changed workflow takes 15–20
minutes of data collection before the catalog sees it. Publish, wait,
<em>then</em> test, or you&rsquo;ll debug a ghost.</li>
<li><strong>Wrapper inputs are duplicated by necessity.</strong> vRO requires every
sub-workflow input to be passed explicitly, so adding an input to a
component means updating the wrapper&rsquo;s call too. Null-guards in each
component turn a forgotten field into a loud failure instead of a silent
default.</li>
</ul>
<h2 id="why-bother">Why bother?</h2>
<p>Because the payoff compounds. Once a full VCF instance is a catalog request,
everything downstream changes character:</p>
<ul>
<li>upgrade rehearsals happen on freshly built instances instead of precious
pets;</li>
<li>a broken environment is redeployed, not repaired;</li>
<li>the lab stops being a collection of snowflakes and becomes a <em>product</em>:
versioned, validated, reproducible.</li>
</ul>
<p><img alt="Three pods, identical IP plans, no route between them" loading="lazy" src="/images/product-01-hook.jpg">
<em>Where this is heading: the factory&rsquo;s output feeding per-student pods with identical addressing.</em></p>
<p>The factory&rsquo;s next customers, funnily enough, are the isolated VPC pods from
<a href="/series/the-vpc-pod-papers/">the other series on this blog</a>. Same
philosophy, one layer further down.</p>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>A complete VCF instance from one form changes what an environment costs
to have. Environments used to be precious, because building one took a
week. Now they&rsquo;re disposable, and a lot follows from that:</p>
<ul>
<li><strong>Proofs of concept</strong> run on an environment built for the customer&rsquo;s
scenario, not on whatever happens to be free.</li>
<li><strong>Upgrade and migration rehearsals</strong> happen on a fresh instance of the
right version, then it&rsquo;s deleted.</li>
<li><strong>Training and enablement</strong> get a real VCF per person or per team.</li>
<li><strong>Reference builds</strong> exist for every supported release, on demand.</li>
</ul>
<p>This is how Comms-care provides a dedicated instance to every consultant.
The same factory, pointed at a customer&rsquo;s requirements, is a repeatable
way to deliver environments, rather than a one-off project each time.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>Derive names, subnets and VLANs from a single environment number; generate
specs, never hand-edit them.</li>
<li>Respect the request-duration ceiling: start long work, return a task
handle, re-attach to watch. Never block a wrapper on an hours-long task.</li>
<li><code>validateOnly</code> on every item, cascaded by the wrapper: dry-run the whole
stack before touching anything.</li>
<li>Componentise failure: one broken step re-runs alone.</li>
<li>Pre-create DNS; expect HCL friction on virtual hardware; budget for vRO&rsquo;s
content-source lag.</li>
</ul>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/deployment/deploying-a-new-vmware-cloud-foundation-or-vmware-vsphere-foundation-private-cloud-/use-a-json-specification-to-deploy-vmware-cloud-foundation-or-vmware-vsphere-foundation.html">Use a JSON Specification File to Deploy VMware Cloud Foundation or vSphere Foundation</a>: deploying VCF 9.1 from a JSON spec, which the installer validates first</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/planning-and-preparation/vcf-components-fqdns-and-ip-addresses/first-vcf-instance-fqdns-and-ip-addresses.html">First VCF Instance FQDNs and IP addresses</a>: the FQDNs, static IPs and forward and reverse DNS every component needs</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/working-with-the-vcf-automation-catalog/service-broker-adding-content-to-the-catalog/service-broker-add-vrealize-orchestrator-workflows-to-the-catalog.html">Add VCF Operations Orchestrator Client workflows to the VCF Automation catalog</a>: vRO workflows as catalog items, through an Orchestrator content source</li>
<li><a href="https://knowledge.broadcom.com/external/article/408300/vsan-esa-deployment-override-hcl-validat.html">vSAN ESA Deployment: Override HCL Validation for Non-Certified Hardware</a>: the installer&rsquo;s vSAN ESA disk check against the HCL, and the documented override</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/release-notes/vmware-cloud-foundation-9-1-0-0-release-notes/vmware-cloud-foundation-bill-of-materials.html">Bill of Materials 9.1.0</a>: the components and builds of VCF 9.1.0</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/getting-started-with-organizations-for-vm-apps-in-vcf-automation/map-head-projects-adding-and-managing-projects/projects-how-do-i-add-a-project-for-my-development-team.html">Add a project for your VCF Automation for VM Apps development team</a>: a project&rsquo;s request Timeout on its Provisioning tab, two hours by default</li>
</ul>
<hr>
<p><em>Lab environment; opinions my own. The automation described builds nested
VCF 9.1 instances for lab and rehearsal use — patterns transfer, specifics
are ours.</em></p>
]]></content:encoded>
    </item>
    <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 pass it down. The most valuable checkbox in the lab factory.</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;.</p>
<p>The alternative is finding out at 2am, two hours into a bringup, that a
hostname doesn&rsquo;t resolve. Two hours is a long time to wait for bad news from
DNS.</p>
<p>vRO workflows don&rsquo;t come with a dry run. This is the case for adding one to
every workflow 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>, which defaults to false. When it&rsquo;s 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 is the one form that chains hosts, bringup, supervisor, fleet
components and identity. It has the same checkbox, and it <strong>cascades</strong> it to
every component. Tick everything, tick validateOnly, request.</p>
<p>Thirty seconds to a few minutes later, you have a full-stack plan against the
<em>live</em> environment, and nothing has moved. It&rsquo;s the most productive way I
know of doing nothing.</p>
<h2 id="what-it-caught">What it caught</h2>
<p>This isn&rsquo;t hypothetical. On an already-built environment, the cascaded dry
run of the whole stack reported:</p>
<ul>
<li>edge cluster: <strong>already exists</strong>; correctly recorded, and the wrapper
carried on</li>
<li>Ops for Logs: <strong>IP_IN_USE</strong> on the planned address. Quite 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, and a reassuringly dull one.</p>
<p>On a <em>fresh</em> environment, the same run has caught, at various times:</p>
<ul>
<li>a DNS record missing for one of about 40 required names. The installer&rsquo;s
own pre-flight found it in seconds, instead of bringup finding it in hour
two.</li>
<li>a stale content-library image ID.</li>
<li>a form field arriving <code>null</code> because a custom form hadn&rsquo;t finished
re-importing. That&rsquo;s a <em>publishing</em> bug, and the dry run surfaced it
before anyone requested anything real.</li>
</ul>
<h2 id="the-argument-against-answered">The argument against, answered</h2>
<p>&ldquo;It doubles the code.&rdquo; It doesn&rsquo;t. It puts the <code>if (!validateOnly)</code> guard
around the mutating call, and the validation logic is code you should have
had anyway.</p>
<p>What it <em>does</em> force is separating &ldquo;compute what to do&rdquo; from &ldquo;do it&rdquo;. That&rsquo;s
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, which the test environment has never had the pleasure of
meeting.</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>, and you can run it on
every change.</p>
<p>The factory&rsquo;s smoke runner does exactly that. It requests every item with
<code>validateOnly: true</code>, asserts <code>CREATE_SUCCESSFUL</code>, and diffs the plan summary
against the last run. It takes minutes. It has caught more bugs in the
workflows than any amount of code review, which says something about the
bugs, or possibly about my code reviews.</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.</p>
<p>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>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/deployment/deploying-a-new-vmware-cloud-foundation-or-vmware-vsphere-foundation-private-cloud-/use-a-json-specification-to-deploy-vmware-cloud-foundation-or-vmware-vsphere-foundation.html">Use a JSON Specification File to Deploy VMware Cloud Foundation or vSphere Foundation</a>: the installer&rsquo;s own validation of a spec, with errors and warnings, before deployment</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/planning-and-preparation/vcf-components-fqdns-and-ip-addresses/first-vcf-instance-fqdns-and-ip-addresses.html">First VCF Instance FQDNs and IP addresses</a>: the names and forward and reverse DNS records every VCF component needs</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/working-with-the-vcf-automation-catalog/service-broker-adding-content-to-the-catalog/service-broker-add-vrealize-orchestrator-workflows-to-the-catalog.html">Add VCF Operations Orchestrator Client workflows to the VCF Automation catalog</a>: vRO workflows as catalog items, through an Orchestrator content source</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>
    <item>
      <title>Porting a PowerShell deploy script to a catalog item: the mapping table is the post</title>
      <link>https://thenestedlab.com/posts/porting-a-powershell-deploy-script/</link>
      <pubDate>Wed, 16 Sep 2026 06:20:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/porting-a-powershell-deploy-script/</guid>
      <description>A 400-line menu-driven PowerShell script became a cloud template, two vRO actions and two subscriptions. Where each behaviour belongs, four non-obvious decisions, and the &amp;lsquo;yes&amp;rsquo; that isn&amp;rsquo;t &amp;rsquo;true&amp;rsquo;.</description>
      <content:encoded><![CDATA[<p>Every lab has one: the script that builds the nested hosts. Ours was
<code>esxihostdeploy.ps1</code>, ovftool plus PowerCLI. An interactive menu asked for
environment, ESX version, role, size and host count. Then came a loop of
<code>ovftool --prop:guestinfo.*</code>, <code>Set-VM</code>, <code>New-NetworkAdapter</code> and
<code>Set-HardDisk</code>.</p>
<p>It worked for years. It also prompted for credentials, lived on one
machine, and knew nothing about the bringup that came after. Loyal, if a
little needy.</p>
<p>Porting it to a VCF Automation catalog item (VM Apps: a cloud template
plus vRO) is not a rewrite. It&rsquo;s a <strong>sorting exercise</strong>. Every behaviour in
the script has a natural home in the declarative model, and the skill is
finding it. Here&rsquo;s the whole table, then the four rows that took some
thought.</p>
<h2 id="the-mapping">The mapping</h2>
<table>
	<thead>
			<tr>
					<th>Script behaviour</th>
					<th>Where it lives now</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Environment menu (f01–f10)</td>
					<td><code>environment</code> input (enum)</td>
			</tr>
			<tr>
					<td>Version menu → OVA path on a share</td>
					<td><code>esxVersion</code> input → <strong>image mapping</strong> → content library item</td>
			</tr>
			<tr>
					<td>Role menu (management / workload / both)</td>
					<td><code>role</code> input; &ldquo;both&rdquo; = two requests</td>
			</tr>
			<tr>
					<td>Size menu / auto-detect from an existing host</td>
					<td><code>size</code> input (auto-detect dropped — see below)</td>
			</tr>
			<tr>
					<td>vCenter + ESXi credential prompts</td>
					<td>vCenter creds gone (cloud account); <code>esxiRootPassword</code> an encrypted input</td>
			</tr>
			<tr>
					<td>Next-free <code>esxNN</code> index scan (gap-filling)</td>
					<td>vRO <strong>action</strong> <code>getNextEsxHostIndexes</code>, bound to the request form</td>
			</tr>
			<tr>
					<td>VLAN / IP / gateway arithmetic</td>
					<td><strong>template expressions</strong> (same formulas)</td>
			</tr>
			<tr>
					<td><code>ovftool --prop:guestinfo.*</code></td>
					<td><code>ovfProperties</code> on <code>Cloud.vSphere.Machine</code></td>
			</tr>
			<tr>
					<td>Folder lookup / <code>New-Folder</code></td>
					<td><strong>allocation-phase subscription</strong> creates the folder if missing</td>
			</tr>
			<tr>
					<td><code>Set-VM</code> cpu / mem</td>
					<td><code>cpuCount</code> / <code>totalMemoryMB</code> in the template</td>
			</tr>
			<tr>
					<td>2× <code>New-NetworkAdapter</code> (Vmxnet3)</td>
					<td><code>networks</code> array, <code>deviceIndex</code> 0/1/2</td>
			</tr>
			<tr>
					<td>3× <code>Set-HardDisk</code> grow</td>
					<td><strong>post-provision subscription</strong></td>
			</tr>
			<tr>
					<td><code>NestedHVEnabled = $true</code></td>
					<td>post-provision subscription</td>
			</tr>
			<tr>
					<td>&ldquo;Power on after?&rdquo; prompt</td>
					<td><code>powerOn</code> input, honoured post-provision</td>
			</tr>
			<tr>
					<td>Summary table printed at the end</td>
					<td>the deployment view in the UI</td>
			</tr>
	</tbody>
</table>
<p>That&rsquo;s five homes, in decreasing order of preference:</p>
<ol>
<li><strong>input</strong></li>
<li><strong>template expression</strong></li>
<li><strong>platform abstraction</strong> (image mapping, network profile, cloud account)</li>
<li><strong>form action</strong> (read-only lookup at request time)</li>
<li><strong>subscription</strong> (imperative work at a lifecycle stage)</li>
</ol>
<p>Push each behaviour as far up that list as it will go.</p>
<h2 id="the-four-decisions-that-werent-obvious">The four decisions that weren&rsquo;t obvious</h2>
<h3 id="1-drop-auto-detect-the-platform-already-remembers">1. Drop auto-detect; the platform already remembers</h3>
<p>The script inspected an existing host to work out its size. That was a
workaround for having no record. The catalog <em>is</em> the record: deployment
history shows what size every existing host was requested at. So the input
simply asks. Fewer moving parts, and the requester sees the choice.</p>
<h3 id="2-the-index-scan-is-a-form-action-not-a-workflow-step">2. The index scan is a form action, not a workflow step</h3>
<p>&ldquo;Next free <code>esx07</code>&rdquo; has to be known <em>at request time</em>, so the requester
sees the names they&rsquo;ll get. That makes it a <strong>vRO action bound to the
custom form</strong>, not a step inside provisioning:
<code>getNextEsxHostIndexes(environment, role, count)</code> returns an array of
strings. Forms can call actions, so use one for anything that&rsquo;s a lookup.</p>
<h3 id="3-rename-and-folder-go-in-compute-allocation-hardware-goes-in-post-provision">3. Rename and folder go in <em>Compute Allocation</em>, hardware goes in <em>Post Provision</em></h3>
<p>Two blocking subscriptions, filtered by a custom property on the template:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Subscription 1</th>
					<th>Subscription 2</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Topic</td>
					<td>Compute allocation</td>
					<td>Compute post provision</td>
			</tr>
			<tr>
					<td>Runnable</td>
					<td>&ldquo;Set VM Name &amp; Folder&rdquo;</td>
					<td>&ldquo;Finalize Hardware&rdquo;</td>
			</tr>
			<tr>
					<td>Does</td>
					<td>sets <code>resourceNames</code>, creates folder</td>
					<td>grows 3 disks, <code>NestedHVEnabled</code>, power on</td>
			</tr>
			<tr>
					<td>Timeout</td>
					<td>10 min</td>
					<td>30 min</td>
			</tr>
	</tbody>
</table>
<p>The rename <em>must</em> happen at allocation. It&rsquo;s the only stage where a
workflow output named <code>resourceNames</code> is applied to the machine. Growing
the disks and enabling nested hardware virtualisation both need a VM that
exists, so they wait for post-provision. Both subscriptions are blocking:
the deployment doesn&rsquo;t proceed until they return.</p>
<h3 id="4-nestedesx-yes--not-true">4. <code>nestedEsx: 'yes'</code> — not <code>true</code></h3>
<p>The subscription condition is
<code>event.data.customProperties.nestedEsx == &quot;yes&quot;</code>. Why not <code>&quot;true&quot;</code>?
Because boolean-looking strings can arrive in the event payload as typed
booleans, and <code>true == &quot;true&quot;</code> is false in the condition evaluator <em>and</em>
in the vRO code.</p>
<p>It fails silently: the subscription just never fires. <code>yes</code> can&rsquo;t be
coerced. Small thing; two hours. The ratio still stings.</p>
<h2 id="what-stayed-exactly-the-same">What stayed exactly the same</h2>
<p>The formulas. <code>10.(20+X).&lt;sub&gt;.0/24</code>, VLAN <code>2X0n</code>, gateway <code>.254</code>: they
were string concatenation in PowerShell, and they&rsquo;re template expressions
now, character for character. The <code>guestinfo.*</code> property names are
identical too, because the OVA didn&rsquo;t change.</p>
<p>Porting a script well means most of it survives. Only the <em>plumbing</em>
moves.</p>
<h2 id="the-bits-that-still-bite">The bits that still bite</h2>
<ul>
<li><strong>Network profile without IP ranges.</strong> Addressing comes in through
<code>guestinfo</code>, not the platform&rsquo;s IP address management (IPAM). Tag the
trunk port group and add no ranges, or IPAM and guestinfo will disagree,
each utterly sure of itself.</li>
<li><strong>Two template revisions in the repo.</strong> v1 is what the guide documents;
v2 grew later. Both are kept deliberately, and both are labelled. Check
which one the org has <em>imported</em> before editing either.</li>
<li><strong>Content-source lag.</strong> A new or changed vRO action needs about 15–20
minutes of data collection before the form sees it. Publish, wait, then
test.</li>
<li><strong>Small disks and OSDATA.</strong> Nested hosts with 64 GB disks ship
ESX-OSDATA at essentially the whole disk. Templates now provision 128 GB,
and a relocate-scratch script limits the damage on existing hosts.</li>
</ul>
<p><img alt="The Nested ESX request form: environment, version, role, size, count — and Host Indexes already computed by the form action" loading="lazy" src="/images/ui/f6-f00-nested-esx-form.jpg">
<em>Every menu prompt from the script is now a field; <code>Host Indexes</code> is the form action&rsquo;s answer to &ldquo;next free esxNN&rdquo;.</em></p>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>Almost every organisation has these scripts: valuable, trusted, and stuck
on one person&rsquo;s machine. The message of this post for them is that
modernising doesn&rsquo;t mean rewriting.</p>
<p>The logic survives. What changes is where it lives: behind a request form,
with access control, an audit trail, consistent inputs and a deployment
record. That&rsquo;s how a team turns tribal knowledge into a service, without
losing the years of edge cases the script already handles.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>Porting is <strong>sorting</strong>: input, then expression, then platform
abstraction, then form action, then subscription. Push each behaviour as
far up as it goes.</li>
<li>Lookups the requester needs to <em>see</em> are <strong>form actions</strong>.</li>
<li>Rename at <strong>allocation</strong> (<code>resourceNames</code> output); hardware at
<strong>post-provision</strong>. Both blocking.</li>
<li>Filter subscriptions on a custom property, and make its value a word
that can&rsquo;t be coerced to a boolean.</li>
<li>Drop workarounds for missing state; the catalog is the state.</li>
<li>Keep the formulas. Move the plumbing.</li>
</ul>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/working-with-the-vcf-automation-catalog/maphead-designing-your-deployments/other-code-examples/vsphere-resource-examples.html">vSphere resource examples in VCF Automation for VM Apps</a>: <code>Cloud.vSphere.Machine</code> with CPU and memory, several NICs, a vCenter folder and <code>ovfProperties</code></li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/getting-started-with-organizations-for-vm-apps-in-vcf-automation/maphead-build-resource-infrastructure/mappings-how-to-add-image-mappings.html">How to add image mapping in VCF Automation for VM Apps to access common operating systems</a>: image mappings, named images per cloud account and region</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/working-with-the-vcf-automation-catalog/service-broker-custom-forms-customize-a-request-form/service-broker-custom-forms-learn-more-about-service-broker-custom-forms/service-broker-custom-forms-using-vro-actions-in-the-custom-form-designer.html">Using VCF Operations orchestrator actions in the custom form designer in VCF Automation for VM Apps</a>: form fields filled by a vRO action that takes other fields as inputs</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/working-with-the-vcf-automation-catalog/maphead-designing-your-deployments/maphead-extensibility-in-cloud-assembly/learn-more-about-extensibilty-subscriptions/event-topics-provided-with-cloud-assembly.html">Event topics provided with VCF Automation for VM Apps in</a>: the Compute allocation topic, where resource names can still change, and Compute post provision</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/working-with-the-vcf-automation-catalog/maphead-designing-your-deployments/maphead-extensibility-in-cloud-assembly/learn-more-about-extensibilty-subscriptions/create-an-extensibility-subscription.html">Create an extensibility subscription</a>: conditions on <code>event.data</code>, blocking, and the workflow a subscription runs</li>
</ul>
<p><em>Part of <a href="/series/the-lab-factory/">The Lab Factory</a>. Next: <a href="/series/the-lab-factory/">driving the
VCF Installer API from vRO</a>.</em></p>
<hr>
<p><em>Lab environment; opinions my own.</em></p>
]]></content:encoded>
    </item>
    <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 turns an environment number into a VCF 9.1 deployment spec, validates it and starts bringup, because the request dies long before the eight-hour build does.</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 and the fleet
components. The VCF 9.1 Installer appliance does that through an API, from a
deployment spec of a few hundred lines of JSON.</p>
<p>This post is the vRO workflow that drives it. Three design constraints
shaped it:</p>
<ul>
<li>nobody edits the spec by hand;</li>
<li>the request gets two hours by default;</li>
<li>nested hosts fail hardware validation.</li>
</ul>
<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
<a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/working-with-the-vcf-automation-catalog/service-broker-adding-content-to-the-catalog/service-broker-add-vrealize-orchestrator-workflows-to-the-catalog.html">Orchestrator content source</a>.</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). 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>Some things are static across environments: DNS, NTP, subdomain, component
sizes, vSAN ESA FTT=1, and the component build versions pinned to the
installer binaries.</p>
<p>One lab password feeds every credential field. The UI export scrubs them,
and the action puts them back as the API schema expects.</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.
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. In seconds, the installer will tell you that
<code>f03-m01-nsx01.res.lab</code> doesn&rsquo;t resolve, that an IP is in use, or that a
host isn&rsquo;t reachable. Two hours into a bringup is a bad time to learn that.
Seconds in, it&rsquo;s merely embarrassing.</p>
<p>Every one of the roughly 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>. The arithmetic is not
encouraging. When the token runs out, the workflow dies with
<code>Delegating token is not service token</code>.</p>
<p>The project&rsquo;s
<a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/getting-started-with-organizations-for-vm-apps-in-vcf-automation/map-head-projects-adding-and-managing-projects/projects-how-do-i-add-a-project-for-my-development-team.html">request timeout</a>
also defaults to two hours, and its Provisioning tab can raise it. We never
tried, because eight hours is too long to hold a request open.</p>
<p>So by default the workflow is fire-and-forget: <code>waitForCompletion=false</code>,
return the task id, and 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> (the hosts, then bringup, then the day-N
components that need bringup to be finished) has a neater trick.</p>
<p>The catalog-bound parent deploys the hosts, submits bringup, and then
<strong>re-executes itself as a plain vRO run</strong> (Orchestrator &gt; Run: no catalog
token, no two-hour kill). It carries the hidden <code>bringupWatchTaskId</code> with
it. That continuation polls the installer task to completion (eight hours,
fine), and then runs certificates, fleet items, edge, supervisor and
identity.</p>
<p>Watch it under <em>Orchestrator &gt; Activity &gt; 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
compatibility list (HCL) check blocks the virtual NVMe controller. Fix it
at the vSphere Lifecycle Manager (vLCM) layer:
<code>enforce_hcl_validation=false</code> on the image policy. The vSAN health test
<code>nvmeonhcl</code> also complains. We silence it through the vSAN API,
best-effort, with a 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 with &ldquo;No compatible DVS&rdquo; on a fresh
instance, wait and re-run just that item. Patience, it turns out, is a
deployment step.</li>
<li><strong>TSM-SSH.</strong> Bringup wants SSH on the hosts, so the wrapper enables it
host-direct through 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 three
things happen. The form shows the new field. The request records its value.
And the workflow receives <strong>null</strong>, because Service Broker keeps the old
request schema until the content source re-imports. Two out of three, which
here counts as a fail.</p>
<p>The workflow null-guards every boolean, and aborts with &ldquo;inputs not mapped&rdquo;
rather than running with silently wrong options. The fix: re-import the
content source, confirm the schema, and submit a <em>new</em> request.
Resubmitting an old one reuses the old payload.</p>
<p>There are actually three asynchronous layers between &ldquo;publish&rdquo; and
&ldquo;mappable request&rdquo;:</p>
<ul>
<li>vRO processing the import;</li>
<li>the catalog schema after re-import;</li>
<li>the form service still enforcing the previous custom form for a minute
or two.</li>
</ul>
<p>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.</p>
<p>Generating the specification from a validated reference removes a whole
class of errors: the ones that come from editing hundreds of lines of JSON
by hand. 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 loudly. 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>
<h2 id="broadcom-documentation">Broadcom documentation</h2>
<ul>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/deployment/deploying-a-new-vmware-cloud-foundation-or-vmware-vsphere-foundation-private-cloud-/use-a-json-specification-to-deploy-vmware-cloud-foundation-or-vmware-vsphere-foundation.html">Use a JSON Specification File to Deploy VMware Cloud Foundation or vSphere Foundation</a>: the deployment spec, its validation and retrying failed tasks; points to the VCF Installer API reference</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/planning-and-preparation/vcf-components-fqdns-and-ip-addresses/first-vcf-instance-fqdns-and-ip-addresses.html">First VCF Instance FQDNs and IP addresses</a>: the FQDNs and forward and reverse DNS records to plan for every component</li>
<li><a href="https://knowledge.broadcom.com/external/article/408300/vsan-esa-deployment-override-hcl-validat.html">vSAN ESA Deployment: Override HCL Validation for Non-Certified Hardware</a>: the installer&rsquo;s vSAN ESA HCL check, and the documented override for non-certified disks</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/working-with-the-vcf-automation-catalog/service-broker-adding-content-to-the-catalog/service-broker-add-vrealize-orchestrator-workflows-to-the-catalog.html">Add VCF Operations Orchestrator Client workflows to the VCF Automation catalog</a>: a vRO workflow as a catalog item, through an Orchestrator content source</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/release-notes/vmware-cloud-foundation-9-1-0-0-release-notes/vmware-cloud-foundation-bill-of-materials.html">Bill of Materials 9.1.0</a>: the component versions and builds of VCF 9.1.0</li>
<li><a href="https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-1/organization-management/vcfa-overview/getting-started-with-organizations-for-vm-apps-in-vcf-automation/map-head-projects-adding-and-managing-projects/projects-how-do-i-add-a-project-for-my-development-team.html">Add a project for your VCF Automation for VM Apps development team</a>: a project&rsquo;s request Timeout on its Provisioning tab, two hours by default</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>
