Azure / Azure/bicep-registry-modules

[AVM Module Issue]: avm/res/azure-stack-hci/cluster - Support pre-baked VM image for e2e test infrastructure to reduce 5+ hour pipeline to ~2 hours

Open
#6,775 9 comments 0 reactions 6 assignees Claimed by @PrithwiTech View on GitHub
Class: Resource Module :package: Type: AVM :a: :v: :m: Type: Feature Request :heavy_plus_sign:
Dominant language
Bicep
Stars
736
Forks
564
Avg merge
3d 15h
Merged PRs (30d)
30

Description

### Check for previous/existing GitHub issues

- [x] I have checked for previous/existing GitHub issues

### Issue Type?

Feature Request

### Module Name

avm/res/azure-stack-hci/cluster

### (Optional) Module Version

0.4.0

### Description

## Feature Request: Support Pre-Baked VM Image for E2E Test Infrastructure

### Problem Statement

The `avm/res/azure-stack-hci/cluster` module's e2e deployment tests currently take **5+ hours per test case**, frequently breaching GitHub Actions' **6-hour hard limit**. This causes CI failures unrelated to code quality and blocks PR merges.

Each test run rebuilds the entire nested virtualization environment from scratch — downloading a 3 GB HCI ISO, converting it to VHDX, installing AD DS, DNS, DHCP, Hyper-V roles, and installing Az PowerShell modules (~400 MB) — even though this infrastructure is identical across every run.

### Time Breakdown of Rebuild-from-Scratch Steps

| Step | What's rebuilt every time | Duration |
|------|-------------------------|----------|
| Stage 1 | Install Hyper-V, DNS, DHCP, Clustering roles | ~10 min |
| Stage 3 | Download 3 GB HCI ISO from Microsoft CDN | ~10-15 min |
| Stage 3 | Convert ISO → VHDX (WIM extraction) | ~10 min |
| Stage 3 | Install AD DS Forest (`Install-ADDSForest`) | ~5-10 min |
| Stage 6 | `Install-Module Az` (~400 MB from PSGallery) | ~5-10 min |
| **Total rebuild** | | **~40-55 min per test** |

These steps are **100% deterministic** — same inputs produce same outputs. They could be eliminated by using a pre-baked VM image.

### Proposed Solution

Use a **pre-baked Azure VM image** (via Azure Compute Gallery / Shared Image Gallery) for the host VM instead of starting from a bare Windows Server 2022 image. The pre-baked image would include:

1. **Hyper-V, DNS, DHCP, Clustering roles** — pre-installed
2. **HCI OS VHDX** — pre-cached at a known path (e.g., `C:\ISOs\hci_os.vhdx`)
3. **Az PowerShell modules** — pre-installed
4. **AD DS** — either pre-configured or ready for fast initialization

A working prototype already exists in the repo at:
**`utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/hciHostDeploymentWithImage.bicep`**

This file demonstrates the approach using a Shared Image Gallery reference:

```bicep
param imageReferenceId string = '/SharedGalleries/b9e38f20-7c9c-4497-a25d-1a0c5eef2108-DIRECTLYSHARING/Images/vhci-Generalized/Versions/latest'

resource vm 'Microsoft.Compute/virtualMachines@2024-11-01' = {
properties: {
storageProfile: {
imageReference: !empty(imagePublisher)
? { publisher: imagePublisher, offer: imageOffer, sku: imageSku, version: imageVersion }
: { sharedGalleryImageId: imageReferenceId }
}
}
}
```

Key improvements in the prototype:
- **Smaller VM size**: Uses `Standard_D16as_v7` instead of `Standard_E32bds_v5` (significant cost reduction)
- **Only 3 runCommands**: `wait` → `ad` (AD provisioning) → `arc1`/`arc2` (Arc onboarding, in parallel) — vs. 8 sequential stages currently
- **No ISO download/conversion**: VHDX pre-baked into the image
- **No role installation**: All roles pre-installed in the image
- **Parallel Arc onboarding**: `arc1` and `arc2` run simultaneously (currently sequential in Stage 6)

### Expected Impact

| Metric | Current | With Pre-Baked Image |
|--------|---------|---------------------|
| Host setup time | ~2.5 hours (Stages 1-7) | ~30-40 min |
| VM size required | Standard_E32bds_v5 (256 GB RAM) | Standard_D16as_v7 (64 GB RAM) |
| Total test time | ~5+ hours | ~2-3 hours |
| CI cost per run | ~$15 (E32bds_v5 × 5h) | ~$3 (D16as_v7 × 2.5h) |
| Stages eliminated | 0 | Stages 1, 3 (partial), 6 (partial) |

### Requirements for Upstream Support

To adopt this approach in the AVM CI pipeline, we need:

1. **Azure Compute Gallery** provisioned in the CI subscription with the pre-baked HCI host image
2. **Image build pipeline** — automated image rebuild when HCI OS version changes (currently `AzureStackHCI23H2.25398.469`)
3. **CI pipeline support** — the `avm-validateModuleDeployment` action and `Test-ModuleLocally.ps1` need to support passing custom image references to test deployments
4. **Shared Image Gallery ID** made available as a CI variable (e.g., `vars.HCI_HOST_IMAGE_ID`)

### Additional Context

- The prototype `hciHostDeploymentWithImage.bicep` is already functional and has been validated
- The Jumpstart LocalBox project uses a similar pre-baked VHDX approach with `azcopy` from blob storage, confirming this pattern works at scale
- The module is already on the OIDC exception list due to its unique deployment requirements (see related issue for OIDC)
- The HCI e2e test is the **longest-running test in the entire AVM Bicep registry** — no other module comes close to 5+ hours

---

## AVM CI/CD Constraints That Make This Critical

The AVM Bicep framework imposes specific CI/CD requirements that make the current 5+ hour test time untenable. These are not optional — they are **spec-mandated constraints** that the HCI cluster module must operate within.

### Constraint 1: GitHub-Hosted Runners with 6-Hour Hard Limit

AVM Bicep modules run on **`ubuntu-latest` GitHub-hosted runners** ([workflow reference](https://github.com/Azure/bicep-registry-modules/blob/main/.github/workflows/avm.template.module.yml#L40)). GitHub enforces a **non-configurable 6-hour maximum job duration**. The `avm.template.module.yml` sets `timeout-minutes: 600` but GitHub's own 6-hour hard limit overrides this.

Current HCI cluster tests take **5-6 hours**, leaving zero margin for any variance. Even a 5-minute platform delay can cause a timeout.

### Constraint 2: OIDC Authentication (Federated Credentials)

Per AVM spec, all modules should authenticate via **OIDC (OpenID Connect)** using federated credentials — no long-lived secrets. The HCI cluster module is currently on the [OIDC exception list](https://github.com/Azure/bicep-registry-modules/blob/main/.github/actions/templates/avm-validateModuleDeployment/action.yml#L101-L110), requiring a service principal + secret. This diverges from the standard and adds operational overhead.

### Constraint 3: Any Subscription / No Pre-Provisioned Resources

AVM e2e tests must be **self-contained** — they create all resources from scratch in a fresh resource group and clean up afterwards. Tests cannot rely on pre-existing resources in the subscription. This is why the test creates the entire nested HCI environment from a bare VM each time.

Relevant code: [avm-validateModuleDeployment/action.yml](https://github.com/Azure/bicep-registry-modules/blob/main/.github/actions/templates/avm-validateModuleDeployment/action.yml) — the deployment action creates a new RG, deploys, validates, and removes.

### Constraint 4: Mandatory `defaults` and `waf-aligned` Tests

AVM resource modules **must** have both `defaults` and `waf-aligned` e2e test folders ([AVM spec BCPRMNFR1](https://azure.github.io/Azure-Verified-Modules/specs/bcp/res/)). The Pester static validation [enforces this](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/pipelines/staticValidation/compliance/module.tests.ps1#L314) and blocks `.e2eignore` on these folders for `res/` modules. Both tests run the full 5+ hour deployment.

### Constraint 5: Idempotent Re-Run (Skipped Due to RP Limitation)

AVM tests normally deploy twice to verify idempotency. The HCI module [explicitly skips this](https://github.com/Azure/bicep-registry-modules/blob/main/avm/res/azure-stack-hci/cluster/tests/e2e/defaults/main.test.bicep#L77) because the `deploymentSettings` RP rejects re-deployment (see [Issue #6776](https://github.com/Azure/bicep-registry-modules/issues/6776)).

---

## Complete Code Reference Links

### Upstream Repository (Azure/bicep-registry-modules — `main` branch)

| File | Purpose | Link |
|------|---------|------|
| `hciHostDeploymentWithImage.bicep` | **Pre-baked image deployment** (existing, working) | [View](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/hciHostDeploymentWithImage.bicep) |
| `hciHostDeployment.bicep` | **Current from-scratch deployment** (8 sequential stages) | [View](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/hciHostDeployment.bicep) |
| `provision-ad.ps1` | Lightweight AD provisioning (for image-based path) | [View](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/provision-ad.ps1) |
| `provision-arc.ps1` | Streamlined Arc onboarding (parallel per node) | [View](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/provision-arc.ps1) |
| `wait.ps1` | Parameterized wait script | [View](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/wait.ps1) |
| `hciHostStage1.ps1` | Install Hyper-V roles (current, eliminated by image) | [View](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/hciHostStage1.ps1) |
| `hciHostStage3.ps1` | ISO download + AD install (current, eliminated by image) | [View](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/hciHostStage3.ps1) |
| `hciHostStage5.ps1` | Create HCI node VMs (current) | [View](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/hciHostStage5.ps1) |
| `hciHostStage6.ps1` | Arc onboarding (current, sequential) | [View](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/hciHostStage6.ps1) |
| `hciHostStage7.ps1` | Extension wait (current) | [View](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/hciHostStage7.ps1) |
| `dependencies.bicep` | Test dependency orchestrator | [View](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/e2e-template-assets/module-specific/azure-stack-hci/dependencies/dependencies.bicep) |
| `main.test.bicep` (defaults) | Default config e2e test | [View](https://github.com/Azure/bicep-registry-modules/blob/main/avm/res/azure-stack-hci/cluster/tests/e2e/defaults/main.test.bicep) |
| `main.test.bicep` (waf-aligned) | WAF-aligned config e2e test | [View](https://github.com/Azure/bicep-registry-modules/blob/main/avm/res/azure-stack-hci/cluster/tests/e2e/waf-aligned/main.test.bicep) |
| `deployment-setting.bicep` | Validate → Deploy orchestrator | [View](https://github.com/Azure/bicep-registry-modules/blob/main/avm/res/azure-stack-hci/cluster/modules/deployment-setting.bicep) |
| `main.bicep` (module) | The AVM module under test | [View](https://github.com/Azure/bicep-registry-modules/blob/main/avm/res/azure-stack-hci/cluster/main.bicep) |
| `avm.template.module.yml` | Reusable CI workflow (6h runner) | [View](https://github.com/Azure/bicep-registry-modules/blob/main/.github/workflows/avm.template.module.yml) |
| `avm-validateModuleDeployment` | Deployment validation action (OIDC exception list) | [View](https://github.com/Azure/bicep-registry-modules/blob/main/.github/actions/templates/avm-validateModuleDeployment/action.yml) |
| `module.tests.ps1` | Pester tests (enforces defaults/waf-aligned, blocks .e2eignore) | [View](https://github.com/Azure/bicep-registry-modules/blob/main/utilities/pipelines/staticValidation/compliance/module.tests.ps1#L314) |

### Fork with Reliability Improvements (chirag1603/bicep-registry-modules — `avm-res-azure-stack-hci-cluster` branch)

| File | Change | Link |
|------|--------|------|
| `hciHostDeployment.bicep` | Added Stage 8 pre-deploy check, replaced wait2 with AD health poll, reduced wait1 | [View](https://github.com/chirag1603/bicep-registry-modules/blob/avm-res-azure-stack-hci-cluster/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/hciHostDeployment.bicep) |
| `hciHostWait2-adHealthPoll.ps1` | AD health poll replacing fixed 300s sleep | [View](https://github.com/chirag1603/bicep-registry-modules/blob/avm-res-azure-stack-hci-cluster/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/hciHostWait2-adHealthPoll.ps1) |
| `hciHostStage7.ps1` | Added network connectivity validation from nodes to Azure endpoints | [View](https://github.com/chirag1603/bicep-registry-modules/blob/avm-res-azure-stack-hci-cluster/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/hciHostStage7.ps1) |
| `hciHostStage8-preDeployCheck.ps1` | New pre-deploy health gate (AD, DNS, VMs, WinRM, Arc, credentials) | [View](https://github.com/chirag1603/bicep-registry-modules/blob/avm-res-azure-stack-hci-cluster/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/hciHostStage8-preDeployCheck.ps1) |
| `hciHostStage5.ps1` | Replaced 300s VM boot sleep with heartbeat polling | [View](https://github.com/chirag1603/bicep-registry-modules/blob/avm-res-azure-stack-hci-cluster/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/hciHostStage5.ps1) |
| `hciHostStage6.ps1` | Replaced 600s edge device sleep with immediate polling | [View](https://github.com/chirag1603/bicep-registry-modules/blob/avm-res-azure-stack-hci-cluster/utilities/e2e-template-assets/module-specific/azure-stack-hci/azureStackHCIHost/scripts/hciHostStage6.ps1) |

### PRs and CI Runs

| Reference | Link |
|-----------|------|
| PR #6687 (HCI cluster module) | [View PR](https://github.com/Azure/bicep-registry-modules/pull/6687) |
| CI Run #91 (waf-aligned failure — ECE Lite) | [View Run](https://github.com/Azure/bicep-registry-modules/actions/runs/23588931245/job/68689243530) |
| Original pre-baked image PR (#5189) | [View PR](https://github.com/Azure/bicep-registry-modules/pull/5189) |
| Stabilization PR (#5412) | [View PR](https://github.com/Azure/bicep-registry-modules/pull/5412) |
| Arc timeout fix PR (#5208) | [View PR](https://github.com/Azure/bicep-registry-modules/pull/5208) |
| Related Issue: OIDC + Idempotency | [#6776](https://github.com/Azure/bicep-registry-modules/issues/6776) |

### (Optional) Correlation Id

N/A

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.