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

symphony (both pipeline stages, both DataSource outputs)

Runs on

The single virt-metal node — see OpenShift Virtualization

Media source

installer-repo (in-cluster PVC-backed nginx, WebDAV-enabled) — never an external URL

Outputs, in order

DataSource ws2019-base (stage 1) → DataSource ws2019-symphony (stage 2)

GitOps ownership

Pipeline definitions are Argo-owned; PipelineRun`s are deliberately excluded from `kustomization.yaml — Argo must never auto-submit a run, each is `oc create -f’d by hand when its prerequisites are met

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

installer-repo (in-cluster nginx)

outbound from pipeline steps

HTTP 80

Fetch the WS2019 eval ISO, autounattend/customize ConfigMap manifests, pre-extracted egocore-4.0.0.0.msi / soamcore-7.3.2.0.msi

Tekton Hub resolver to artifacthub.io

outbound

HTTPS 443

Resolve the windows-efi-installer@4.20.7 pipeline definition and its component Tasks

registry.redhat.io

outbound

HTTPS 443

virtio-win container disk image pull (virtio-win-rhel9)

CDI importer

outbound to installer-repo

HTTP

ISO/DataVolume import into gp3-csi

Stage-2 customize VM to installer-repo

outbound (guest)

HTTP 80

install-symphony.ps1 downloads the two pre-extracted MSIs directly

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.exe is an InstallShield self-extractor, not the InstallAnywhere .bin its Linux twin is — its --extract mode 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 to installer-repo over WebDAV, and install-symphony.ps1 simply `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, not egocore-7.3.2.0.msi as 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 with msitools.

  • PowerShell/XML encoding trap: a BOM-less .ps1 with 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.tmpl answer 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: VirtualMachinePool shares one sysprep ConfigMap across every replica, so unattend-join.xml.tmpl uses <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.