Windows golden image pipeline
A two-stage Tekton pipeline that produces the golden Windows Server 2019 disk image every Symphony compute VM clones from: a vanilla WS2019 + virtio + qemu-guest-agent base, then the Symphony 7.3.2 compute-host software (unjoined, sysprep-generalized) on top. Rebuilding the image is always one pipeline run, never a hand-fixed disk.
Purpose
The output is a single DataSource (ws2019-symphony) that the
Windows compute pool's VirtualMachinePool clones on
every scale-out.
WS2019 eval ISO ──┐
virtio-win ISO ────┤ Stage 1: windows-efi-installer (Tekton, hub-resolved)
autounattend.xml ──┘ → WS2019 + virtio + qemu-guest-agent (~16 min measured)
output: DataSource ws2019-base
ws2019-base ────────┐ Stage 2: ws2019-symphony-customize (in-repo Pipeline)
egocore/soamcore msi boot temp VM from ws2019-base
(pre-extracted, → install-symphony.ps1: msiexec /qn both MSIs
served by installer-repo) → sysprep /generalize /oobe /shutdown
output: DataSource ws2019-symphony
Supportability precision, stated plainly: OpenShift Pipelines is the Red
Hat-supported platform running these pipelines; the windows-efi-installer pipeline
definition itself is an upstream KubeVirt community example pipeline
(kubevirt/kubevirt-tekton-tasks), consumed via the Tekton Hub redhat-pipelines
catalog — it is not a Red-Hat-productized artifact, and it does not perform sysprep or
generalization itself. Generalization is entirely stage 2’s job, adapted in-repo.
Placement
| Property | Value |
|---|---|
Namespace |
|
Runs on |
The single |
Media source |
|
Outputs, in order |
|
GitOps ownership |
Pipeline definitions are Argo-owned; |
No secrets are baked into the golden image: hostname, master address, join entitlement,
and passwords are all deliberately absent from ws2019-symphony — per-VM identity is
injected only at clone time. The Windows local Administrator password is substituted
from an OpenShift Secret (win-admin) at render time, never committed as a literal.
Both pipeline runs execute as the pipeline ServiceAccount in symphony, bound to the
namespace-scoped edit ClusterRole, not cluster-admin.
Interfaces
| Peer | Direction | Port / Protocol | Purpose |
|---|---|---|---|
|
outbound from pipeline steps |
HTTP 80 |
Fetch the WS2019 eval ISO, autounattend/customize ConfigMap manifests, pre-extracted
|
Tekton Hub resolver to |
outbound |
HTTPS 443 |
Resolve the |
|
outbound |
HTTPS 443 |
virtio-win container disk image pull ( |
CDI importer |
outbound to |
HTTP |
ISO/DataVolume import into |
Stage-2 customize VM to |
outbound (guest) |
HTTP 80 |
|
Maturity
-
Stage-1 timing measured clean: 15m56s end-to-end, well inside the documented 20-60 min range, on the fast end.
-
Stage-2’s silent-install method (ADR 0011) was the single largest source of build time.
symeval-7.3.2.0.exeis an InstallShield self-extractor, not the InstallAnywhere.binits Linux twin is — its--extractmode is a real, non-headless 3-click wizard that a titleless Windows "Networks" shell flyout can silently block. The durable fix: the two compute-host MSIs are extracted once, on a throwaway scratch VM, published toinstaller-repoover WebDAV, andinstall-symphony.ps1simply `msiexec /qn`s the pre-published files on every run — zero self-extraction, zero UI automation. -
Naming trap: the real extracted filename is
egocore-4.0.0.0.msi, notegocore-7.3.2.0.msias every doc/script assumed before 2026-07-10 — versioned against the EGO base release, not the Symphony product version. Discovered only by reading the MSI’s own tables withmsitools. -
PowerShell/XML encoding trap: a BOM-less
.ps1with a UTF-8 em-dash silently mis-decodes under PowerShell 5.1’s legacy cp1252 fallback into a smart quote the tokenizer treats as a string delimiter, aborting the script with no log output. The blanket fix then broke.xml.tmplanswer files a different way — XML 1.0 forbids a literal--inside<!-- -→comments. Lesson baked into build discipline, not yet a repo lint gate. -
Known gap, not silently resolved:
VirtualMachinePoolshares one sysprep ConfigMap across every replica, sounattend-join.xml.tmpluses<ComputerName>*</ComputerName>(Windows auto-generates a unique name) rather than a per-clone hostname. Verify joined hosts by IP from qemu-guest-agent until a per-replica ConfigMap mechanism exists. -
Stage-2 expected duration ~30 min per
images/windows/README.md; timeout left generous (2h) as OOBE/sysprep variance headroom — tighten once a live post-pivot run’s actual duration is recorded.
Verify
# 1. Stage-1 output exists and is Ready
oc get datasource ws2019-base -n symphony
# 2. Stage-2 output exists and is Ready (the golden image compute VMs will clone)
oc get datasource ws2019-symphony -n symphony
# 3. Watch a stage-2 run's install-symphony.ps1 progress live off COM1 (no VNC rig needed)
virtctl console <stage-2-customize-vm-name> -n symphony
# 4. Confirm the pre-extracted MSIs are actually being served
oc run msi-check --rm -i --restart=Never --image=registry.access.redhat.com/ubi9/ubi-minimal -n symphony -- \
curl -sI http://installer-repo.symphony.svc/windows/msi/egocore-4.0.0.0.msi \
http://installer-repo.symphony.svc/windows/msi/soamcore-7.3.2.0.msi
# 5. Confirm the resolved pipeline version is still the latest 4.20.x (catalog drift check)
curl -s https://artifacthub.io/api/v1/packages/tekton-pipeline/redhat-pipelines/windows-efi-installer \
| jq .available_versions
Continue to Symphony management, the master pair each compute VM clone joins.