The nested-esxi-pod blueprint works. Getting there took seven distinct “ContentValid: False” (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.
1. ${input.x} is illegal inside a flow mapping
This looks like valid YAML and valid blueprint syntax:
- {key: guestinfo.hostname, value: {value: "esx01.${input.podName}.res.lab"}}
It fails content validation. The expression parser doesn’t reach into
flow-style ({...}) mappings. Block style is fine:
- key: guestinfo.hostname
value:
value: esx01.${input.podName}.res.lab
Mixed style in the same list is fine too — only the entries that carry an
expression need to be block-style. (This is why the blueprint’s
vAppConfig list looks inconsistent; it’s deliberate.)
2. name vs generateName for a new namespace
A CCI.Supervisor.Namespace you’re creating must use generateName.
metadata.name is rejected by the CCI API — the platform appends a random
suffix, so pod-a- becomes pod-a-dgf5p. Everything downstream should
reference ${resource.namespace.id}, never a literal name.
3. Zones and storage classes are flat
Early attempts wrapped them the way the raw CCI API does:
initialClassConfigOverrides:
zones: [...]
In a blueprint they’re top-level properties of the namespace resource:
zones:
- name: domain-c9
cpuLimit: 40000M
memoryLimit: 64000Mi
storageClasses:
- name: vSAN Default Storage Policy
limit: 400000Mi
And zones are required — omit them and the API says “Zone should be specified”, which at least is a clear message.
4. A new namespace has no content library
Deploy the namespace, deploy a VM, and get: no VirtualMachineImage
found. A VCFA-created namespace attaches no content libraries by
default. The fix is one block:
contentSources:
- {name: ISO, type: ContentLibrary}
- {name: f06-vks-lib01, type: ContentLibrary}
Without it you’re in the vSphere Client attaching libraries to a namespace by hand, which rather defeats the catalog.
5. Images sync after attach — wait for status.disks
Even with libraries attached at creation, the first VM create in a fresh namespace can be rejected:
no disks found in image ... status.disks
The image objects appear immediately; their disk metadata syncs over the
next 1–3 minutes. The quota webhook checks status.disks and refuses
until it’s populated. In a blueprint, put the hosts dependsOn something
that takes a couple of minutes (the binding maps did the job here), or add
an explicit wait. In a script, poll:
kubectl get virtualmachineimage -n <ns> <vmi> -o jsonpath='{.status.disks}'
6. Validation lives in status, not the HTTP code
Creating a BlueprintVersion returns 200 whether or not the content is
valid. Read the object back:
status:
contentValid: false
validationMessages:
- "... unexpected token ..."
If your pipeline checks the response code, it will happily publish a
broken blueprint. Check status.contentValid and print the messages.
7. Only one published version — 409 on the second
Release 1.1.0 while 1.0.0 is released and you get a 409. It isn’t a
transient conflict; it’s the rule. Unrelease the current version, then
release the new one. Practically that means a publish step is
unrelease old → release new, and there’s a short window where the
catalog item has no released version. Do it when nobody’s requesting.
Bonus: the things that aren’t blueprint problems
Three prerequisites have no blueprint resource type and have to exist
before the request — VPC, VPCAttachment, LoadBalancer, in that
order. The blueprint’s vpcName
input says “must exist and be Realized”, and it means it. Nothing in the
blueprint fails if they’re missing; the deployment just never gets a VIP.
Why this matters outside the lab
VCF Automation’s All Apps model is new, and new platforms have edges. None of these seven are documented; all of them stall a first project by days if you meet them cold. The value of a delivery partner who has already built on the platform isn’t the YAML — it’s that a customer’s first blueprint publishes on day one instead of week two, and that the sharp edges are encoded into templates and provisioning scripts where users never meet them.
Rules learned
- Expressions need block-style YAML; flow mappings don’t get parsed.
generateName, and reference the namespace by${resource.x.id}.zonesandstorageClassesare flat and zones are required.contentSourceson the namespace, or nothing can be deployed.- Wait for image
status.disksbefore the first VM (1–3 min). - Check
status.contentValid— the HTTP code lies by omission. - One released version per blueprint: unrelease, then release.
Companion to a datacenter in a catalog tile.
Lab environment; opinions my own. Error text captured live.