From 3ac47b64999b0d501459f630301a38f5a462af29 Mon Sep 17 00:00:00 2001 From: Tim Wright Date: Sat, 30 May 2026 17:29:51 +1200 Subject: [PATCH 1/6] strip gallery lineage stuff --- .pipelines/scripts/windows_build_vhd.sh | 142 +++++++++++++++++------- 1 file changed, 101 insertions(+), 41 deletions(-) diff --git a/.pipelines/scripts/windows_build_vhd.sh b/.pipelines/scripts/windows_build_vhd.sh index b6ac3472d2c..969f4bdf7df 100644 --- a/.pipelines/scripts/windows_build_vhd.sh +++ b/.pipelines/scripts/windows_build_vhd.sh @@ -24,7 +24,6 @@ set +x # * SKIPVALIDATEREOFFERUPDATE - is always set to True # * BUILD_DATE - # First we validate the branch. DRY_RUN is only allowed to be false on release branches - which are of the form # windows/vYYYYMMDD. If we're on the release branch then we also override SIG_FOR_PRODUCTION, because this is a production build. # for dry runs, we set SIG_FOR_PRODUCTION to false. @@ -34,34 +33,34 @@ echo "Checking SourceBranch: ${BRANCH}" # Check if IS_RELEASE_PIPELINE is already set in the environment if [ -z "${IS_RELEASE_PIPELINE:-}" ]; then - if echo "${BRANCH}" | grep -E '^refs/heads/windows/v[[:digit:]]{8}$' > /dev/null; then - echo "The branch ${BRANCH} is a release branch. Setting IS_RELEASE_PIPELINE to True." - export IS_RELEASE_PIPELINE="True" - echo "##vso[task.setvariable variable=IS_RELEASE_PIPELINE]True" - else - echo "The branch ${BRANCH} is not a release branch. Setting IS_RELEASE_PIPELINE to False." - export IS_RELEASE_PIPELINE="False" - echo "##vso[task.setvariable variable=IS_RELEASE_PIPELINE]False" - fi + if echo "${BRANCH}" | grep -E '^refs/heads/windows/v[[:digit:]]{8}$' >/dev/null; then + echo "The branch ${BRANCH} is a release branch. Setting IS_RELEASE_PIPELINE to True." + export IS_RELEASE_PIPELINE="True" + echo "##vso[task.setvariable variable=IS_RELEASE_PIPELINE]True" + else + echo "The branch ${BRANCH} is not a release branch. Setting IS_RELEASE_PIPELINE to False." + export IS_RELEASE_PIPELINE="False" + echo "##vso[task.setvariable variable=IS_RELEASE_PIPELINE]False" + fi fi if [ "${IS_RELEASE_PIPELINE}" = "True" ]; then - if [ "${DRY_RUN}" = "True" ]; then - echo "This is a test build triggered from the release pipeline" - else - echo "This is a release build triggered from the release pipeline. DRY_RUN=${DRY_RUN}" - - if ! (echo "${BRANCH}" | grep -E '^refs/heads/windows/v[[:digit:]]{8}$' > /dev/null); then - echo "The branch ${BRANCH} is not release branch. Please use the release branch. Release branch name format: windows/vYYYYMMDD." - exit 1 + if [ "${DRY_RUN}" = "True" ]; then + echo "This is a test build triggered from the release pipeline" + else + echo "This is a release build triggered from the release pipeline. DRY_RUN=${DRY_RUN}" + + if ! (echo "${BRANCH}" | grep -E '^refs/heads/windows/v[[:digit:]]{8}$' >/dev/null); then + echo "The branch ${BRANCH} is not release branch. Please use the release branch. Release branch name format: windows/vYYYYMMDD." + exit 1 + fi + echo "##vso[task.setvariable variable=SIG_FOR_PRODUCTION]True" fi - echo "##vso[task.setvariable variable=SIG_FOR_PRODUCTION]True" - fi else - echo "This is a test build triggered from the test pipeline" - export DRY_RUN=True - echo "##vso[task.setvariable variable=DRY_RUN]$DRY_RUN"; - echo "##vso[task.setvariable variable=SIG_FOR_PRODUCTION]False" + echo "This is a test build triggered from the test pipeline" + export DRY_RUN=True + echo "##vso[task.setvariable variable=DRY_RUN]$DRY_RUN" + echo "##vso[task.setvariable variable=SIG_FOR_PRODUCTION]False" fi export MODE="windowsVhdMode" @@ -74,32 +73,33 @@ echo "Original SIG_IMAGE_VERSION: ${SIG_IMAGE_VERSION:-}" # -n is "not empty" if [ -n "${SIG_GALLERY_NAME:-}" ] && [ -n "${SIG_IMAGE_NAME_PREFIX:-}" ] && [ -n "${SIG_IMAGE_VERSION:-}" ]; then - echo "All of Name, Prefix, and Version have been set" - export SIG_IMAGE_NAME="${SIG_IMAGE_NAME_PREFIX}-${WINDOWS_SKU}" + echo "All of Name, Prefix, and Version have been set" + export SIG_IMAGE_NAME="${SIG_IMAGE_NAME_PREFIX}-${WINDOWS_SKU}" else - echo "At least on of the name, prefix or version are empty. Overwriting all values. " - export SIG_IMAGE_VERSION="$(date +"%y%m%d").$(date +"%H%M%S").$RANDOM" - export SIG_IMAGE_NAME="windows-${WINDOWS_SKU}" - export SIG_GALLERY_NAME="PackerSigGalleryEastUS" + echo "At least on of the name, prefix or version are empty. Overwriting all values. " + export SIG_IMAGE_VERSION="$(date +"%y%m%d").$(date +"%H%M%S").$RANDOM" + export SIG_IMAGE_NAME="windows-${WINDOWS_SKU}" + export SIG_GALLERY_NAME="PackerSigGalleryEastUS" - export WS_SKU=$(echo $WINDOWS_SKU | tr '-' '_') + export WS_SKU=$(echo $WINDOWS_SKU | tr '-' '_') fi if [ "${USE_RELEASE_DATE:-}" = "False" ]; then - echo "use current date as build date"; BUILD_DATE=$(date +"%y%m%d") + echo "use current date as build date" + BUILD_DATE=$(date +"%y%m%d") else - echo "use release date as build date" - echo "${RELEASE_DATE:-}" | grep -E '[[:digit:]]{6}' - if (( $? != 0 )); then - echo "The release date ${RELEASE_DATE} is not valid date. Release date format: YYMMDD." - exit 1 - fi - export BUILD_DATE=${RELEASE_DATE} + echo "use release date as build date" + echo "${RELEASE_DATE:-}" | grep -E '[[:digit:]]{6}' + if (($? != 0)); then + echo "The release date ${RELEASE_DATE} is not valid date. Release date format: YYMMDD." + exit 1 + fi + export BUILD_DATE=${RELEASE_DATE} fi echo "Default BUILD_DATE is $BUILD_DATE" if [ -n "${CUSTOM_BUILD_DATE:-}" ]; then - echo "set BUILD_DATE to ${CUSTOM_BUILD_DATE}" - export BUILD_DATE=${CUSTOM_BUILD_DATE} + echo "set BUILD_DATE to ${CUSTOM_BUILD_DATE}" + export BUILD_DATE=${CUSTOM_BUILD_DATE} fi echo "Modified SIG_IMAGE_VERSION: ${SIG_IMAGE_VERSION}" @@ -120,6 +120,66 @@ export MANAGED_SIG_ID="$(cat packer-output | grep -a "ManagedImageSharedImageGal echo "Found OS_DISK_URI: ${OS_DISK_URI}" echo "Found MANAGED_SIG_ID: ${MANAGED_SIG_ID}" +# Break gallery lineage if the build was sourced from a shared gallery. +# Sometimes Azure requires AutomaticOSUpgrade on VMSS created from images with gallery lineage. +# Re-creating the SIG image version from a managed disk severs that association. +sig_source_gallery_name=$(jq -r ".WindowsBaseVersions.\"${WINDOWS_SKU}\".sig_source_gallery_name // empty" /dev/null || sleep 30 + + echo "Re-creating SIG image version from managed disk (no gallery lineage)..." + az sig image-version create \ + --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ + --gallery-name "${SIG_GALLERY_NAME}" \ + --gallery-image-definition "${SIG_IMAGE_NAME}" \ + --gallery-image-version "${SIG_IMAGE_VERSION}" \ + --os-snapshot "${DISK_ID}" \ + --location "${LOCATION}" \ + --replica-count 1 \ + -o none + + NEW_SIG_ID="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AZURE_RESOURCE_GROUP_NAME}/providers/Microsoft.Compute/galleries/${SIG_GALLERY_NAME}/images/${SIG_IMAGE_NAME}/versions/${SIG_IMAGE_VERSION}" + echo "New SIG image version (lineage-free): ${NEW_SIG_ID}" + export MANAGED_SIG_ID="${NEW_SIG_ID}" + + echo "Cleaning up temporary disk..." + az disk delete --resource-group "${AZURE_RESOURCE_GROUP_NAME}" --name "${LINEAGE_DISK_NAME}" --yes --no-wait + + echo "Gallery lineage successfully broken" +else + echo "Build was not sourced from a gallery — no lineage breaking needed" +fi + # if bash is echoing the commands, then ADO processes both the echo of the command to set the variable and the command itself. # This causes super odd behavior in ADO. set +x From 1318b8529ac029a3f10b16e4157d27ff5feb1a62 Mon Sep 17 00:00:00 2001 From: Tim Wright Date: Sat, 30 May 2026 17:43:08 +1200 Subject: [PATCH 2/6] wait --- .pipelines/scripts/windows_build_vhd.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pipelines/scripts/windows_build_vhd.sh b/.pipelines/scripts/windows_build_vhd.sh index 969f4bdf7df..6e648dcdce3 100644 --- a/.pipelines/scripts/windows_build_vhd.sh +++ b/.pipelines/scripts/windows_build_vhd.sh @@ -173,7 +173,7 @@ if [ -n "${sig_source_gallery_name}" ] && [ -n "${MANAGED_SIG_ID}" ]; then export MANAGED_SIG_ID="${NEW_SIG_ID}" echo "Cleaning up temporary disk..." - az disk delete --resource-group "${AZURE_RESOURCE_GROUP_NAME}" --name "${LINEAGE_DISK_NAME}" --yes --no-wait + az disk delete --resource-group "${AZURE_RESOURCE_GROUP_NAME}" --name "${LINEAGE_DISK_NAME}" --yes -o none echo "Gallery lineage successfully broken" else From 847759a97d527814e5620e8c36aadd9628ab70d2 Mon Sep 17 00:00:00 2001 From: Tim Wright Date: Sat, 30 May 2026 23:36:59 +1200 Subject: [PATCH 3/6] copy tags over --- .pipelines/scripts/windows_build_vhd.sh | 33 +++++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/.pipelines/scripts/windows_build_vhd.sh b/.pipelines/scripts/windows_build_vhd.sh index 6e648dcdce3..2cc6cfa3638 100644 --- a/.pipelines/scripts/windows_build_vhd.sh +++ b/.pipelines/scripts/windows_build_vhd.sh @@ -142,6 +142,15 @@ if [ -n "${sig_source_gallery_name}" ] && [ -n "${MANAGED_SIG_ID}" ]; then DISK_ID="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AZURE_RESOURCE_GROUP_NAME}/providers/Microsoft.Compute/disks/${LINEAGE_DISK_NAME}" echo "Created disk: ${DISK_ID}" + echo "Capturing tags from original SIG image version..." + ORIGINAL_TAGS=$(az sig image-version show \ + --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ + --gallery-name "${SIG_GALLERY_NAME}" \ + --gallery-image-definition "${SIG_IMAGE_NAME}" \ + --gallery-image-version "${SIG_IMAGE_VERSION}" \ + --query "tags" -o json 2>/dev/null || echo "{}") + echo "Captured tags: ${ORIGINAL_TAGS}" + echo "Deleting original SIG image version..." az sig image-version delete \ --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ @@ -158,15 +167,23 @@ if [ -n "${sig_source_gallery_name}" ] && [ -n "${MANAGED_SIG_ID}" ]; then --deleted 2>/dev/null || sleep 30 echo "Re-creating SIG image version from managed disk (no gallery lineage)..." - az sig image-version create \ - --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ - --gallery-name "${SIG_GALLERY_NAME}" \ - --gallery-image-definition "${SIG_IMAGE_NAME}" \ - --gallery-image-version "${SIG_IMAGE_VERSION}" \ - --os-snapshot "${DISK_ID}" \ - --location "${LOCATION}" \ - --replica-count 1 \ + CREATE_ARGS=( + --resource-group "${AZURE_RESOURCE_GROUP_NAME}" + --gallery-name "${SIG_GALLERY_NAME}" + --gallery-image-definition "${SIG_IMAGE_NAME}" + --gallery-image-version "${SIG_IMAGE_VERSION}" + --os-snapshot "${DISK_ID}" + --location "${LOCATION}" + --replica-count 1 -o none + ) + if [ "${ORIGINAL_TAGS}" != "{}" ] && [ -n "${ORIGINAL_TAGS}" ]; then + CREATE_ARGS+=(--tags) + while IFS="=" read -r key value; do + CREATE_ARGS+=("${key}=${value}") + done < <(echo "${ORIGINAL_TAGS}" | jq -r 'to_entries[] | "\(.key)=\(.value)"') + fi + az sig image-version create "${CREATE_ARGS[@]}" NEW_SIG_ID="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AZURE_RESOURCE_GROUP_NAME}/providers/Microsoft.Compute/galleries/${SIG_GALLERY_NAME}/images/${SIG_IMAGE_NAME}/versions/${SIG_IMAGE_VERSION}" echo "New SIG image version (lineage-free): ${NEW_SIG_ID}" From abfe373e7070d8d1fb579f88da9550a84feceac1 Mon Sep 17 00:00:00 2001 From: Tim Wright Date: Sun, 31 May 2026 12:01:28 +1200 Subject: [PATCH 4/6] fix: enable AutomaticOSUpgrade on VMSS for 1P gallery images Azure requires AutomaticOSUpgradePolicy.EnableAutomaticOSUpgrade=true when creating a VMSS from an official 1P gallery image. Without this, VMSS creation fails with: "Virtual Machine Scale Set using official 1P gallery cannot be created without enabling AutomaticOSUpgrade." Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- e2e/types.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/e2e/types.go b/e2e/types.go index 9c6a3b177ce..0459d32b9de 100644 --- a/e2e/types.go +++ b/e2e/types.go @@ -239,6 +239,15 @@ func (s *Scenario) PrepareVMSSModel(ctx context.Context, t testing.TB, vmss *arm s.VMConfigMutator(vmss) } + // Enable AutomaticOSUpgrade on the VMSS upgrade policy. + // This is required when the VHD image comes from an official 1P gallery. + if vmss.Properties.UpgradePolicy == nil { + vmss.Properties.UpgradePolicy = &armcompute.UpgradePolicy{} + } + vmss.Properties.UpgradePolicy.AutomaticOSUpgradePolicy = &armcompute.AutomaticOSUpgradePolicy{ + EnableAutomaticOSUpgrade: to.Ptr(true), + } + if vmss.Properties.VirtualMachineProfile == nil { vmss.Properties.VirtualMachineProfile = &armcompute.VirtualMachineScaleSetVMProfile{} } From 6dc751b5991c3ab17b4fa1c7da6a97a61939edc6 Mon Sep 17 00:00:00 2001 From: Tim Wright Date: Wed, 3 Jun 2026 11:44:50 +1200 Subject: [PATCH 5/6] remove copy --- .pipelines/scripts/windows_build_vhd.sh | 76 ------------------------- 1 file changed, 76 deletions(-) diff --git a/.pipelines/scripts/windows_build_vhd.sh b/.pipelines/scripts/windows_build_vhd.sh index 2cc6cfa3638..af6ba2f8ac0 100644 --- a/.pipelines/scripts/windows_build_vhd.sh +++ b/.pipelines/scripts/windows_build_vhd.sh @@ -120,82 +120,6 @@ export MANAGED_SIG_ID="$(cat packer-output | grep -a "ManagedImageSharedImageGal echo "Found OS_DISK_URI: ${OS_DISK_URI}" echo "Found MANAGED_SIG_ID: ${MANAGED_SIG_ID}" -# Break gallery lineage if the build was sourced from a shared gallery. -# Sometimes Azure requires AutomaticOSUpgrade on VMSS created from images with gallery lineage. -# Re-creating the SIG image version from a managed disk severs that association. -sig_source_gallery_name=$(jq -r ".WindowsBaseVersions.\"${WINDOWS_SKU}\".sig_source_gallery_name // empty" /dev/null || echo "{}") - echo "Captured tags: ${ORIGINAL_TAGS}" - - echo "Deleting original SIG image version..." - az sig image-version delete \ - --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ - --gallery-name "${SIG_GALLERY_NAME}" \ - --gallery-image-definition "${SIG_IMAGE_NAME}" \ - --gallery-image-version "${SIG_IMAGE_VERSION}" - - echo "Waiting for deletion to propagate..." - az sig image-version wait \ - --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ - --gallery-name "${SIG_GALLERY_NAME}" \ - --gallery-image-definition "${SIG_IMAGE_NAME}" \ - --gallery-image-version "${SIG_IMAGE_VERSION}" \ - --deleted 2>/dev/null || sleep 30 - - echo "Re-creating SIG image version from managed disk (no gallery lineage)..." - CREATE_ARGS=( - --resource-group "${AZURE_RESOURCE_GROUP_NAME}" - --gallery-name "${SIG_GALLERY_NAME}" - --gallery-image-definition "${SIG_IMAGE_NAME}" - --gallery-image-version "${SIG_IMAGE_VERSION}" - --os-snapshot "${DISK_ID}" - --location "${LOCATION}" - --replica-count 1 - -o none - ) - if [ "${ORIGINAL_TAGS}" != "{}" ] && [ -n "${ORIGINAL_TAGS}" ]; then - CREATE_ARGS+=(--tags) - while IFS="=" read -r key value; do - CREATE_ARGS+=("${key}=${value}") - done < <(echo "${ORIGINAL_TAGS}" | jq -r 'to_entries[] | "\(.key)=\(.value)"') - fi - az sig image-version create "${CREATE_ARGS[@]}" - - NEW_SIG_ID="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${AZURE_RESOURCE_GROUP_NAME}/providers/Microsoft.Compute/galleries/${SIG_GALLERY_NAME}/images/${SIG_IMAGE_NAME}/versions/${SIG_IMAGE_VERSION}" - echo "New SIG image version (lineage-free): ${NEW_SIG_ID}" - export MANAGED_SIG_ID="${NEW_SIG_ID}" - - echo "Cleaning up temporary disk..." - az disk delete --resource-group "${AZURE_RESOURCE_GROUP_NAME}" --name "${LINEAGE_DISK_NAME}" --yes -o none - - echo "Gallery lineage successfully broken" -else - echo "Build was not sourced from a gallery — no lineage breaking needed" -fi # if bash is echoing the commands, then ADO processes both the echo of the command to set the variable and the command itself. # This causes super odd behavior in ADO. From df6ea209663462e186e7f30ddd0ac9ccf837bb62 Mon Sep 17 00:00:00 2001 From: Tim Wright Date: Wed, 3 Jun 2026 12:53:45 +1200 Subject: [PATCH 6/6] try fix --- vhdbuilder/packer/produce-packer-settings-functions.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vhdbuilder/packer/produce-packer-settings-functions.sh b/vhdbuilder/packer/produce-packer-settings-functions.sh index 4c4793b8a0c..3fce022354d 100644 --- a/vhdbuilder/packer/produce-packer-settings-functions.sh +++ b/vhdbuilder/packer/produce-packer-settings-functions.sh @@ -357,13 +357,11 @@ function prepare_windows_vhd() { fi windows_sigmode_direct_shared_gallery_image_id="/SharedGalleries/${sig_source_gallery_name}/Images/${sig_image_name}/Versions/${WINDOWS_IMAGE_VERSION}" - # Clear marketplace and raw VHD source fields — packer requires exactly one source type + # Clear VHD import fields — they conflict with gallery source. + # Keep marketplace fields (publisher/offer/sku/version) populated: Packer's ARM + # template validation still references them even when using direct_shared_gallery_image_id. WINDOWS_IMAGE_URL="" WINDOWS_BASE_IMAGE_URL="" - WINDOWS_IMAGE_PUBLISHER="" - WINDOWS_IMAGE_OFFER="" - WINDOWS_IMAGE_SKU="" - WINDOWS_IMAGE_VERSION="" echo "Using direct shared gallery source:" echo " ID: ${windows_sigmode_direct_shared_gallery_image_id}"