From de9e53125c9caf423d8e51b8753494b3ba7cd46b Mon Sep 17 00:00:00 2001 From: bfjelds Date: Mon, 4 May 2026 12:23:42 -0700 Subject: [PATCH 01/60] Add COSI conversion for ACL VHD images Add a post-build pipeline step that converts AzureContainerLinux (ACL) VHD images to COSI format using ImageCustomizer's convert command (v1.2.0-2). Changes: - New script: convert-vhd-to-cosi.sh downloads ACL VHD from blob storage, runs ImageCustomizer convert (VHD -> COSI), and uploads the COSI file - New make target: convert-vhd-to-cosi in packer.mk - Pipeline vars: set IMG_CUSTOMIZER_CONTAINER and IMG_CUSTOMIZER_VERSION on ACL build jobs in both PR and release pipelines - Builder template: add Convert ACL VHD to COSI step after SIG->classic conversion, conditioned on OS_SKU=AzureContainerLinux - Immutable copy: updated to also copy/remove .cosi files for ACL builds Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/.vsts-vhd-builder-release.yaml | 10 ++ .pipelines/.vsts-vhd-builder.yaml | 10 ++ .../templates/.builder-release-template.yaml | 25 ++++- packer.mk | 3 + .../scripts/convert-vhd-to-cosi.sh | 100 ++++++++++++++++++ 5 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index 0813c490cc1..bfe5c2e6440 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -881,6 +881,11 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' + echo '##vso[task.setvariable variable=SIG_SOURCE_GALLERY_UNIQUE_NAME]b3e01d89-bd55-414f-bbb4-cdfeb2628caa-ACL' + echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_NAME]acl' + echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_VERSION]3.0.20260401' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2.0-2' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -906,6 +911,11 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' + echo '##vso[task.setvariable variable=SIG_SOURCE_GALLERY_UNIQUE_NAME]b3e01d89-bd55-414f-bbb4-cdfeb2628caa-ACL' + echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_NAME]acl-arm64' + echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_VERSION]3.0.20260401' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2.0-2' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: diff --git a/.pipelines/.vsts-vhd-builder.yaml b/.pipelines/.vsts-vhd-builder.yaml index e556a651b49..2cc024e8cbb 100644 --- a/.pipelines/.vsts-vhd-builder.yaml +++ b/.pipelines/.vsts-vhd-builder.yaml @@ -220,6 +220,11 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' + echo '##vso[task.setvariable variable=SIG_SOURCE_GALLERY_UNIQUE_NAME]b3e01d89-bd55-414f-bbb4-cdfeb2628caa-ACL' + echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_NAME]acl' + echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_VERSION]3.0.20260401' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2.0-2' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -242,6 +247,11 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' + echo '##vso[task.setvariable variable=SIG_SOURCE_GALLERY_UNIQUE_NAME]b3e01d89-bd55-414f-bbb4-cdfeb2628caa-ACL' + echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_NAME]acl-arm64' + echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_VERSION]3.0.20260401' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2.0-2' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index 213fffd8f8e..6624f63bace 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -395,6 +395,23 @@ steps: env: RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) + - task: AzureCLI@2 + inputs: + azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + make -f packer.mk convert-vhd-to-cosi + condition: | + and( + succeeded(), + eq(variables.GENERATE_PUBLISHING_INFO, 'True'), + eq(variables['OS_SKU'], 'AzureContainerLinux') + ) + displayName: Convert ACL VHD to COSI + env: + RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) + - task: AzureCLI@2 inputs: azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) @@ -405,7 +422,13 @@ steps: export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" export AZCOPY_CONCURRENCY_VALUE="AUTO" az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.vhd" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --auth-mode login || exit 1 - echo "Successfully copied to immutable container" + echo "Successfully copied VHD to immutable container" + if [ "${OS_SKU}" = "AzureContainerLinux" ]; then + echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi to immutable storage container" + az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.cosi" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --auth-mode login || exit 1 + echo "Successfully copied COSI to immutable container" + azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --recursive=true + fi # Remove old VHD from staging container after copy to immutable container is complete azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --recursive=true condition: | diff --git a/packer.mk b/packer.mk index 65a457d9478..dfb6f930833 100755 --- a/packer.mk +++ b/packer.mk @@ -112,6 +112,9 @@ generate-publishing-info: az-login convert-sig-to-classic-storage-account-blob: az-login @./vhdbuilder/packer/convert-sig-to-classic-storage-account-blob.sh +convert-vhd-to-cosi: az-login + @./vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh + scanning-vhd: az-login @./vhdbuilder/packer/vhd-scanning.sh diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh new file mode 100644 index 00000000000..342a96ec876 --- /dev/null +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -0,0 +1,100 @@ +#!/bin/bash +set -euo pipefail + +# Converts an ACL VHD from blob storage to COSI format using ImageCustomizer, +# then uploads the COSI file back to blob storage. + +required_env_vars=( + "DESTINATION_STORAGE_CONTAINER" + "CAPTURED_SIG_VERSION" + "IMG_CUSTOMIZER_CONTAINER" + "IMG_CUSTOMIZER_VERSION" +) + +for v in "${required_env_vars[@]}" +do + if [ -z "${!v}" ]; then + echo "$v was not set!" + exit 1 + fi +done + +WORK_DIR="$(pwd)/cosi-convert" +mkdir -p "$WORK_DIR/build" "$WORK_DIR/out" + +cleanup() { + echo "Cleaning up working directory $WORK_DIR" + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +VHD_BLOB_URL="${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" +LOCAL_VHD="$WORK_DIR/${CAPTURED_SIG_VERSION}.vhd" +LOCAL_COSI="$WORK_DIR/out/${CAPTURED_SIG_VERSION}.cosi" + +echo "Setting azcopy environment variables" +export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" +export AZCOPY_CONCURRENCY_VALUE="AUTO" +export AZCOPY_LOG_LOCATION="$WORK_DIR/azcopy-log-files/" +export AZCOPY_JOB_PLAN_LOCATION="$WORK_DIR/azcopy-job-plan-files/" +mkdir -p "${AZCOPY_LOG_LOCATION}" +mkdir -p "${AZCOPY_JOB_PLAN_LOCATION}" + +echo "Downloading VHD from ${VHD_BLOB_URL}" +if ! azcopy copy "$VHD_BLOB_URL" "$LOCAL_VHD" --recursive=true; then + azExitCode=$? + shopt -s nullglob + for f in "${AZCOPY_LOG_LOCATION}"/*.log; do + echo "Azcopy log file: $f" + echo "##vso[build.uploadlog]$f" + if grep -q '"level":"Error"' "$f"; then + echo "log file $f contains errors" + echo "##vso[task.logissue type=error]Azcopy log file $f contains errors" + cat "$f" + fi + done + shopt -u nullglob + echo "Failed to download VHD, exiting with code $azExitCode" + exit $azExitCode +fi +echo "Downloaded VHD to ${LOCAL_VHD}" + +echo "Converting VHD to COSI using ImageCustomizer ${IMG_CUSTOMIZER_CONTAINER}:${IMG_CUSTOMIZER_VERSION}" +docker run \ + --rm \ + --interactive \ + --privileged=true \ + -v "$WORK_DIR:/convert" \ + -v /dev:/dev \ + "${IMG_CUSTOMIZER_CONTAINER}:${IMG_CUSTOMIZER_VERSION}" \ + imagecustomizer convert \ + --log-level "debug" \ + --build-dir /convert/build \ + --image-file "/convert/${CAPTURED_SIG_VERSION}.vhd" \ + --output-image-file "/convert/out/${CAPTURED_SIG_VERSION}.cosi" \ + --output-image-format cosi + +if [ ! -f "$LOCAL_COSI" ]; then + echo "##vso[task.logissue type=error]COSI file was not created at ${LOCAL_COSI}" + exit 1 +fi + +echo "Uploading COSI to ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" +if ! azcopy copy "$LOCAL_COSI" "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --recursive=true; then + azExitCode=$? + shopt -s nullglob + for f in "${AZCOPY_LOG_LOCATION}"/*.log; do + echo "Azcopy log file: $f" + echo "##vso[build.uploadlog]$f" + if grep -q '"level":"Error"' "$f"; then + echo "log file $f contains errors" + echo "##vso[task.logissue type=error]Azcopy log file $f contains errors" + cat "$f" + fi + done + shopt -u nullglob + echo "Failed to upload COSI, exiting with code $azExitCode" + exit $azExitCode +fi + +echo "Successfully converted and uploaded COSI: ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" From 377092c015481ee8b9f3b98960b6a553e11b58c1 Mon Sep 17 00:00:00 2001 From: bfjelds Date: Tue, 19 May 2026 13:36:25 -0700 Subject: [PATCH 02/60] Split COSI blob copy into its own pipeline task Move the ACL COSI 'az storage blob copy start' from the VHD immutable copy task into a dedicated task with its own condition gating on OS_SKU=AzureContainerLinux, rather than using a bash if-block inside the VHD copy step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../templates/.builder-release-template.yaml | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index 6624f63bace..ec9a140297d 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -423,12 +423,6 @@ steps: export AZCOPY_CONCURRENCY_VALUE="AUTO" az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.vhd" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --auth-mode login || exit 1 echo "Successfully copied VHD to immutable container" - if [ "${OS_SKU}" = "AzureContainerLinux" ]; then - echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi to immutable storage container" - az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.cosi" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --auth-mode login || exit 1 - echo "Successfully copied COSI to immutable container" - azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --recursive=true - fi # Remove old VHD from staging container after copy to immutable container is complete azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --recursive=true condition: | @@ -443,6 +437,31 @@ steps: ) displayName: Copy to Immutable Storage Container + - task: AzureCLI@2 + inputs: + azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi to immutable storage container" + export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" + export AZCOPY_CONCURRENCY_VALUE="AUTO" + az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.cosi" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --auth-mode login || exit 1 + echo "Successfully copied COSI to immutable container" + azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --recursive=true + condition: | + and( + succeeded(), + eq(variables.ENVIRONMENT, 'tme'), + eq(variables.GENERATE_PUBLISHING_INFO, 'True'), + eq(variables['OS_SKU'], 'AzureContainerLinux'), + or( + eq(variables.RUN_PREFETCH_OPTIMIZATION, 'False'), + eq(variables.PREFETCH_COMPATIBLE, 'False') + ) + ) + displayName: Copy COSI to Immutable Storage Container + - task: DownloadPipelineArtifact@2 displayName: Download Build Performance Program condition: and(succeeded(), eq(variables.ENVIRONMENT, 'tme')) From c63ef74b18eacb9648e9bb3420dd28c88de9c81c Mon Sep 17 00:00:00 2001 From: bfjelds Date: Tue, 19 May 2026 13:39:32 -0700 Subject: [PATCH 03/60] Move COSI convert after immutable copy, revert echo text Move 'Convert ACL VHD to COSI' task to after 'Copy to Immutable Storage Container' so the VHD is safely in immutable storage before conversion. Revert echo to original 'Successfully copied to immutable container'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../templates/.builder-release-template.yaml | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index ec9a140297d..e2d053449e4 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -395,23 +395,6 @@ steps: env: RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) - - task: AzureCLI@2 - inputs: - azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) - scriptType: bash - scriptLocation: inlineScript - inlineScript: | - make -f packer.mk convert-vhd-to-cosi - condition: | - and( - succeeded(), - eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux') - ) - displayName: Convert ACL VHD to COSI - env: - RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) - - task: AzureCLI@2 inputs: azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) @@ -422,7 +405,7 @@ steps: export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" export AZCOPY_CONCURRENCY_VALUE="AUTO" az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.vhd" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --auth-mode login || exit 1 - echo "Successfully copied VHD to immutable container" + echo "Successfully copied to immutable container" # Remove old VHD from staging container after copy to immutable container is complete azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --recursive=true condition: | @@ -437,6 +420,23 @@ steps: ) displayName: Copy to Immutable Storage Container + - task: AzureCLI@2 + inputs: + azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + make -f packer.mk convert-vhd-to-cosi + condition: | + and( + succeeded(), + eq(variables.GENERATE_PUBLISHING_INFO, 'True'), + eq(variables['OS_SKU'], 'AzureContainerLinux') + ) + displayName: Convert ACL VHD to COSI + env: + RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) + - task: AzureCLI@2 inputs: azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) From 0e6785353393c59e653a854feaf3a0e9547ca4f6 Mon Sep 17 00:00:00 2001 From: bfjelds Date: Tue, 19 May 2026 13:42:01 -0700 Subject: [PATCH 04/60] Reorder: Convert COSI, copy COSI, then copy VHD to immutable Move 'Copy to Immutable Storage Container' (VHD) after 'Copy COSI to Immutable Storage Container'. New order: 1. Convert ACL VHD to COSI 2. Copy COSI to Immutable Storage Container 3. Copy to Immutable Storage Container (VHD) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../templates/.builder-release-template.yaml | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index e2d053449e4..f040700b0ce 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -401,24 +401,16 @@ steps: scriptType: bash scriptLocation: inlineScript inlineScript: | - echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd to immutable storage container" - export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" - export AZCOPY_CONCURRENCY_VALUE="AUTO" - az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.vhd" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --auth-mode login || exit 1 - echo "Successfully copied to immutable container" - # Remove old VHD from staging container after copy to immutable container is complete - azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --recursive=true + make -f packer.mk convert-vhd-to-cosi condition: | and( succeeded(), - eq(variables.ENVIRONMENT, 'tme'), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - or( - eq(variables.RUN_PREFETCH_OPTIMIZATION, 'False'), - eq(variables.PREFETCH_COMPATIBLE, 'False') - ) + eq(variables['OS_SKU'], 'AzureContainerLinux') ) - displayName: Copy to Immutable Storage Container + displayName: Convert ACL VHD to COSI + env: + RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) - task: AzureCLI@2 inputs: @@ -426,16 +418,24 @@ steps: scriptType: bash scriptLocation: inlineScript inlineScript: | - make -f packer.mk convert-vhd-to-cosi + echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi to immutable storage container" + export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" + export AZCOPY_CONCURRENCY_VALUE="AUTO" + az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.cosi" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --auth-mode login || exit 1 + echo "Successfully copied COSI to immutable container" + azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --recursive=true condition: | and( succeeded(), + eq(variables.ENVIRONMENT, 'tme'), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux') + eq(variables['OS_SKU'], 'AzureContainerLinux'), + or( + eq(variables.RUN_PREFETCH_OPTIMIZATION, 'False'), + eq(variables.PREFETCH_COMPATIBLE, 'False') + ) ) - displayName: Convert ACL VHD to COSI - env: - RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) + displayName: Copy COSI to Immutable Storage Container - task: AzureCLI@2 inputs: @@ -443,24 +443,24 @@ steps: scriptType: bash scriptLocation: inlineScript inlineScript: | - echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi to immutable storage container" + echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd to immutable storage container" export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" export AZCOPY_CONCURRENCY_VALUE="AUTO" - az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.cosi" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --auth-mode login || exit 1 - echo "Successfully copied COSI to immutable container" - azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --recursive=true + az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.vhd" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --auth-mode login || exit 1 + echo "Successfully copied to immutable container" + # Remove old VHD from staging container after copy to immutable container is complete + azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --recursive=true condition: | and( succeeded(), eq(variables.ENVIRONMENT, 'tme'), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux'), or( eq(variables.RUN_PREFETCH_OPTIMIZATION, 'False'), eq(variables.PREFETCH_COMPATIBLE, 'False') ) ) - displayName: Copy COSI to Immutable Storage Container + displayName: Copy to Immutable Storage Container - task: DownloadPipelineArtifact@2 displayName: Download Build Performance Program From 7dda8aec4da591ec1503c3204f3b6f00a3d56903 Mon Sep 17 00:00:00 2001 From: bfjelds Date: Tue, 19 May 2026 13:42:41 -0700 Subject: [PATCH 05/60] Set IMG_CUSTOMIZER_VERSION to 1.2 Use less specific version tag to pick up patch releases automatically. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/.vsts-vhd-builder-release.yaml | 4 ++-- .pipelines/.vsts-vhd-builder.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index bfe5c2e6440..12f2adea082 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -885,7 +885,7 @@ stages: echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_NAME]acl' echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_VERSION]3.0.20260401' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2.0-2' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -915,7 +915,7 @@ stages: echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_NAME]acl-arm64' echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_VERSION]3.0.20260401' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2.0-2' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: diff --git a/.pipelines/.vsts-vhd-builder.yaml b/.pipelines/.vsts-vhd-builder.yaml index 2cc024e8cbb..0243ac7857b 100644 --- a/.pipelines/.vsts-vhd-builder.yaml +++ b/.pipelines/.vsts-vhd-builder.yaml @@ -224,7 +224,7 @@ stages: echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_NAME]acl' echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_VERSION]3.0.20260401' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2.0-2' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -251,7 +251,7 @@ stages: echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_NAME]acl-arm64' echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_VERSION]3.0.20260401' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2.0-2' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: From cc051f9dcc371ec9079ac680ad9abae09e4226a0 Mon Sep 17 00:00:00 2001 From: bfjelds Date: Tue, 19 May 2026 13:49:29 -0700 Subject: [PATCH 06/60] Remove unused SIG_SOURCE_* variables from ACL jobs SIG_SOURCE_GALLERY_UNIQUE_NAME, SIG_SOURCE_IMAGE_NAME, and SIG_SOURCE_IMAGE_VERSION are set but never consumed by any script or template in the repo. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/.vsts-vhd-builder-release.yaml | 6 ------ .pipelines/.vsts-vhd-builder.yaml | 6 ------ 2 files changed, 12 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index 12f2adea082..4971ff672f8 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -881,9 +881,6 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=SIG_SOURCE_GALLERY_UNIQUE_NAME]b3e01d89-bd55-414f-bbb4-cdfeb2628caa-ACL' - echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_NAME]acl' - echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_VERSION]3.0.20260401' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' displayName: Setup Build Variables @@ -911,9 +908,6 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=SIG_SOURCE_GALLERY_UNIQUE_NAME]b3e01d89-bd55-414f-bbb4-cdfeb2628caa-ACL' - echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_NAME]acl-arm64' - echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_VERSION]3.0.20260401' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' displayName: Setup Build Variables diff --git a/.pipelines/.vsts-vhd-builder.yaml b/.pipelines/.vsts-vhd-builder.yaml index 0243ac7857b..1d0ba4d18fc 100644 --- a/.pipelines/.vsts-vhd-builder.yaml +++ b/.pipelines/.vsts-vhd-builder.yaml @@ -220,9 +220,6 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=SIG_SOURCE_GALLERY_UNIQUE_NAME]b3e01d89-bd55-414f-bbb4-cdfeb2628caa-ACL' - echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_NAME]acl' - echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_VERSION]3.0.20260401' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' displayName: Setup Build Variables @@ -247,9 +244,6 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=SIG_SOURCE_GALLERY_UNIQUE_NAME]b3e01d89-bd55-414f-bbb4-cdfeb2628caa-ACL' - echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_NAME]acl-arm64' - echo '##vso[task.setvariable variable=SIG_SOURCE_IMAGE_VERSION]3.0.20260401' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' displayName: Setup Build Variables From c41a42d3f093f3f7644863c04ad9892e6613eb14 Mon Sep 17 00:00:00 2001 From: bfjelds Date: Tue, 19 May 2026 13:50:49 -0700 Subject: [PATCH 07/60] Add IMG_CUSTOMIZER_* variables to ACL FIPS jobs FIPS jobs were missing IMG_CUSTOMIZER_CONTAINER and IMG_CUSTOMIZER_VERSION, which are required for the COSI conversion step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/.vsts-vhd-builder-release.yaml | 4 ++++ .pipelines/.vsts-vhd-builder.yaml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index 4971ff672f8..b1e7cc812b3 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -935,6 +935,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]True' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -960,6 +962,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]True' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: diff --git a/.pipelines/.vsts-vhd-builder.yaml b/.pipelines/.vsts-vhd-builder.yaml index 1d0ba4d18fc..a4f2dda2de3 100644 --- a/.pipelines/.vsts-vhd-builder.yaml +++ b/.pipelines/.vsts-vhd-builder.yaml @@ -268,6 +268,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]True' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -290,6 +292,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]True' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: From 6288af4fc3312899dd41f973209164570f2a93ef Mon Sep 17 00:00:00 2001 From: bfjelds Date: Tue, 19 May 2026 15:37:35 -0700 Subject: [PATCH 08/60] Add cosi-publishing-info.json generation and pipeline artifact Create generate-cosi-publishing-info.sh that writes cosi-publishing-info.json with the immutable storage URL and image metadata, paralleling generate-vhd-publishing-info.sh for VHDs. Pipeline steps added after 'Copy COSI to Immutable Storage Container': - Generate COSI Publishing Info (bash, no az-login needed) - Publish COSI Publishing Info (artifact: cosi-publishing-info-{artifactName}) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../templates/.builder-release-template.yaml | 25 +++++++++ packer.mk | 3 ++ .../packer/generate-cosi-publishing-info.sh | 51 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 vhdbuilder/packer/generate-cosi-publishing-info.sh diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index f040700b0ce..2f75dc17498 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -437,6 +437,31 @@ steps: ) displayName: Copy COSI to Immutable Storage Container + - bash: | + make -f packer.mk generate-cosi-publishing-info + condition: | + and( + succeeded(), + eq(variables.GENERATE_PUBLISHING_INFO, 'True'), + eq(variables['OS_SKU'], 'AzureContainerLinux') + ) + displayName: Generate COSI Publishing Info + env: + STORAGE_ACCT_BLOB_URL: $(CLASSIC_BLOB) + OFFER_NAME: $(OS_SKU) + + - task: PublishPipelineArtifact@1 + condition: | + and( + succeeded(), + eq(variables.GENERATE_PUBLISHING_INFO, 'True'), + eq(variables['OS_SKU'], 'AzureContainerLinux') + ) + displayName: Publish COSI Publishing Info + inputs: + artifactName: 'cosi-publishing-info-${{ parameters.artifactName }}' + targetPath: 'cosi-publishing-info.json' + - task: AzureCLI@2 inputs: azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) diff --git a/packer.mk b/packer.mk index dfb6f930833..1ae444a7551 100755 --- a/packer.mk +++ b/packer.mk @@ -109,6 +109,9 @@ run-imagecustomizer: az-login generate-publishing-info: az-login @./vhdbuilder/packer/generate-vhd-publishing-info.sh +generate-cosi-publishing-info: + @./vhdbuilder/packer/generate-cosi-publishing-info.sh + convert-sig-to-classic-storage-account-blob: az-login @./vhdbuilder/packer/convert-sig-to-classic-storage-account-blob.sh diff --git a/vhdbuilder/packer/generate-cosi-publishing-info.sh b/vhdbuilder/packer/generate-cosi-publishing-info.sh new file mode 100644 index 00000000000..6b2c80fac3a --- /dev/null +++ b/vhdbuilder/packer/generate-cosi-publishing-info.sh @@ -0,0 +1,51 @@ +#!/bin/bash -e + +# Generate cosi-publishing-info.json with metadata about the COSI artifact +# in immutable storage. Parallels generate-vhd-publishing-info.sh for VHDs. + +required_env_vars=( + "STORAGE_ACCT_BLOB_URL" + "CAPTURED_SIG_VERSION" + "OS_NAME" + "OFFER_NAME" + "SKU_NAME" + "HYPERV_GENERATION" +) + +for v in "${required_env_vars[@]}" +do + if [ -z "${!v}" ]; then + echo "$v was not set!" + exit 1 + fi +done + +if [ -z "$IMAGE_VERSION" ]; then + IMAGE_VERSION=$(date +%Y%m.%d.0) + echo "IMAGE_VERSION was not set, defaulting to ${IMAGE_VERSION}" +fi + +if [ "${ARCHITECTURE,,}" = "arm64" ]; then + IMAGE_ARCH="Arm64" +else + IMAGE_ARCH="x64" +fi + +COSI_NAME="${CAPTURED_SIG_VERSION}.cosi" +cosi_url="${STORAGE_ACCT_BLOB_URL}/${COSI_NAME}" +echo "COSI URL ---> ${cosi_url}" + +cat < cosi-publishing-info.json +{ + "cosi_url": "$cosi_url", + "os_name": "$OS_NAME", + "sku_name": "$SKU_NAME", + "offer_name": "$OFFER_NAME", + "hyperv_generation": "${HYPERV_GENERATION}", + "image_architecture": "${IMAGE_ARCH}", + "image_version": "${IMAGE_VERSION}" +} +EOF + +echo "Generated cosi-publishing-info.json:" +cat cosi-publishing-info.json From 8491224b9614f328091795a84e4b73791248161e Mon Sep 17 00:00:00 2001 From: bfjelds Date: Tue, 19 May 2026 15:41:41 -0700 Subject: [PATCH 09/60] Merge cosi-publishing-info generation into convert-vhd-to-cosi.sh Generate cosi-publishing-info.json (with sha256, size, cosi_url, and image metadata) directly in convert-vhd-to-cosi.sh while the COSI artifact is still on disk. Remove the standalone script and make target. Pipeline order: Convert + generate info -> Publish artifact -> Copy to immutable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../templates/.builder-release-template.yaml | 39 +++++--------- packer.mk | 3 -- .../packer/generate-cosi-publishing-info.sh | 51 ------------------- .../scripts/convert-vhd-to-cosi.sh | 35 +++++++++++++ 4 files changed, 49 insertions(+), 79 deletions(-) delete mode 100644 vhdbuilder/packer/generate-cosi-publishing-info.sh diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index 2f75dc17498..8473f5455f9 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -411,6 +411,20 @@ steps: displayName: Convert ACL VHD to COSI env: RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) + STORAGE_ACCT_BLOB_URL: $(CLASSIC_BLOB) + OFFER_NAME: $(OS_SKU) + + - task: PublishPipelineArtifact@1 + condition: | + and( + succeeded(), + eq(variables.GENERATE_PUBLISHING_INFO, 'True'), + eq(variables['OS_SKU'], 'AzureContainerLinux') + ) + displayName: Publish COSI Publishing Info + inputs: + artifactName: 'cosi-publishing-info-${{ parameters.artifactName }}' + targetPath: 'cosi-publishing-info.json' - task: AzureCLI@2 inputs: @@ -437,31 +451,6 @@ steps: ) displayName: Copy COSI to Immutable Storage Container - - bash: | - make -f packer.mk generate-cosi-publishing-info - condition: | - and( - succeeded(), - eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux') - ) - displayName: Generate COSI Publishing Info - env: - STORAGE_ACCT_BLOB_URL: $(CLASSIC_BLOB) - OFFER_NAME: $(OS_SKU) - - - task: PublishPipelineArtifact@1 - condition: | - and( - succeeded(), - eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux') - ) - displayName: Publish COSI Publishing Info - inputs: - artifactName: 'cosi-publishing-info-${{ parameters.artifactName }}' - targetPath: 'cosi-publishing-info.json' - - task: AzureCLI@2 inputs: azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) diff --git a/packer.mk b/packer.mk index 1ae444a7551..dfb6f930833 100755 --- a/packer.mk +++ b/packer.mk @@ -109,9 +109,6 @@ run-imagecustomizer: az-login generate-publishing-info: az-login @./vhdbuilder/packer/generate-vhd-publishing-info.sh -generate-cosi-publishing-info: - @./vhdbuilder/packer/generate-cosi-publishing-info.sh - convert-sig-to-classic-storage-account-blob: az-login @./vhdbuilder/packer/convert-sig-to-classic-storage-account-blob.sh diff --git a/vhdbuilder/packer/generate-cosi-publishing-info.sh b/vhdbuilder/packer/generate-cosi-publishing-info.sh deleted file mode 100644 index 6b2c80fac3a..00000000000 --- a/vhdbuilder/packer/generate-cosi-publishing-info.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash -e - -# Generate cosi-publishing-info.json with metadata about the COSI artifact -# in immutable storage. Parallels generate-vhd-publishing-info.sh for VHDs. - -required_env_vars=( - "STORAGE_ACCT_BLOB_URL" - "CAPTURED_SIG_VERSION" - "OS_NAME" - "OFFER_NAME" - "SKU_NAME" - "HYPERV_GENERATION" -) - -for v in "${required_env_vars[@]}" -do - if [ -z "${!v}" ]; then - echo "$v was not set!" - exit 1 - fi -done - -if [ -z "$IMAGE_VERSION" ]; then - IMAGE_VERSION=$(date +%Y%m.%d.0) - echo "IMAGE_VERSION was not set, defaulting to ${IMAGE_VERSION}" -fi - -if [ "${ARCHITECTURE,,}" = "arm64" ]; then - IMAGE_ARCH="Arm64" -else - IMAGE_ARCH="x64" -fi - -COSI_NAME="${CAPTURED_SIG_VERSION}.cosi" -cosi_url="${STORAGE_ACCT_BLOB_URL}/${COSI_NAME}" -echo "COSI URL ---> ${cosi_url}" - -cat < cosi-publishing-info.json -{ - "cosi_url": "$cosi_url", - "os_name": "$OS_NAME", - "sku_name": "$SKU_NAME", - "offer_name": "$OFFER_NAME", - "hyperv_generation": "${HYPERV_GENERATION}", - "image_architecture": "${IMAGE_ARCH}", - "image_version": "${IMAGE_VERSION}" -} -EOF - -echo "Generated cosi-publishing-info.json:" -cat cosi-publishing-info.json diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index 342a96ec876..dd174390e99 100644 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -98,3 +98,38 @@ if ! azcopy copy "$LOCAL_COSI" "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_ fi echo "Successfully converted and uploaded COSI: ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" + +# Generate cosi-publishing-info.json for downstream consumption +COSI_SHA256=$(sha256sum "$LOCAL_COSI" | awk '{print $1}') +COSI_SIZE=$(stat -c%s "$LOCAL_COSI") + +if [ -z "$IMAGE_VERSION" ]; then + IMAGE_VERSION=$(date +%Y%m.%d.0) + echo "IMAGE_VERSION was not set, defaulting to ${IMAGE_VERSION}" +fi + +if [ "${ARCHITECTURE,,}" = "arm64" ]; then + IMAGE_ARCH="Arm64" +else + IMAGE_ARCH="x64" +fi + +COSI_NAME="${CAPTURED_SIG_VERSION}.cosi" +cosi_url="${STORAGE_ACCT_BLOB_URL}/${COSI_NAME}" + +cat < cosi-publishing-info.json +{ + "cosi_url": "$cosi_url", + "sha256": "${COSI_SHA256}", + "size_bytes": ${COSI_SIZE}, + "os_name": "$OS_NAME", + "sku_name": "$SKU_NAME", + "offer_name": "$OFFER_NAME", + "hyperv_generation": "${HYPERV_GENERATION}", + "image_architecture": "${IMAGE_ARCH}", + "image_version": "${IMAGE_VERSION}" +} +EOF + +echo "Generated cosi-publishing-info.json:" +cat cosi-publishing-info.json From f2586f051ad89ec446f9f62d8281c93d8fc3fcd5 Mon Sep 17 00:00:00 2001 From: bfjelds Date: Tue, 19 May 2026 15:54:34 -0700 Subject: [PATCH 10/60] Hardcode OS_NAME=Linux in convert-vhd-to-cosi.sh OS_NAME was referenced in cosi-publishing-info.json but never set. ACL COSI artifacts are always Linux, so hardcode it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index dd174390e99..2dde24b741b 100644 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -114,6 +114,9 @@ else IMAGE_ARCH="x64" fi +# OS_NAME is always Linux for ACL COSI artifacts +OS_NAME="Linux" + COSI_NAME="${CAPTURED_SIG_VERSION}.cosi" cosi_url="${STORAGE_ACCT_BLOB_URL}/${COSI_NAME}" From 46307590b5f998b587edd7a7c7f2dc1bc3787548 Mon Sep 17 00:00:00 2001 From: bfjelds Date: Wed, 27 May 2026 13:35:47 -0700 Subject: [PATCH 11/60] fix: set execute permission on convert-vhd-to-cosi.sh Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh old mode 100644 new mode 100755 From 410f24155124de2451b76da6539f4e1e367739b8 Mon Sep 17 00:00:00 2001 From: bfjelds Date: Wed, 27 May 2026 14:53:45 -0700 Subject: [PATCH 12/60] fix: remove redundant 'imagecustomizer' from docker run command The container entrypoint is already 'imagecustomizer', so the first argument should be the subcommand 'convert' directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index 2dde24b741b..d60515c93eb 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -67,7 +67,7 @@ docker run \ -v "$WORK_DIR:/convert" \ -v /dev:/dev \ "${IMG_CUSTOMIZER_CONTAINER}:${IMG_CUSTOMIZER_VERSION}" \ - imagecustomizer convert \ + convert \ --log-level "debug" \ --build-dir /convert/build \ --image-file "/convert/${CAPTURED_SIG_VERSION}.vhd" \ From 774fdf04a32d49eb1b7a3f38a7f5a38b7c69bd39 Mon Sep 17 00:00:00 2001 From: bfjelds Date: Thu, 28 May 2026 11:56:09 -0700 Subject: [PATCH 13/60] Update ImageCustomizer version to 1.3 for ACL builds Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/.vsts-vhd-builder-release.yaml | 8 ++++---- .pipelines/.vsts-vhd-builder.yaml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index b1e7cc812b3..ba52f14a280 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -882,7 +882,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -909,7 +909,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -936,7 +936,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -963,7 +963,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: diff --git a/.pipelines/.vsts-vhd-builder.yaml b/.pipelines/.vsts-vhd-builder.yaml index a4f2dda2de3..8f9f08b230d 100644 --- a/.pipelines/.vsts-vhd-builder.yaml +++ b/.pipelines/.vsts-vhd-builder.yaml @@ -221,7 +221,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -245,7 +245,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -269,7 +269,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -293,7 +293,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.2' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: From 805ec22038037bc0c464a5c98039f8fddd335352 Mon Sep 17 00:00:00 2001 From: bfjelds Date: Fri, 29 May 2026 14:53:36 -0700 Subject: [PATCH 14/60] Update ACL imagecustomizer version from 1.3 to 1.4 --- .pipelines/.vsts-vhd-builder-release.yaml | 8 ++++---- .pipelines/.vsts-vhd-builder.yaml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index ba52f14a280..e863f0edeab 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -882,7 +882,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -909,7 +909,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -936,7 +936,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -963,7 +963,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: diff --git a/.pipelines/.vsts-vhd-builder.yaml b/.pipelines/.vsts-vhd-builder.yaml index 8f9f08b230d..5f192d9d656 100644 --- a/.pipelines/.vsts-vhd-builder.yaml +++ b/.pipelines/.vsts-vhd-builder.yaml @@ -221,7 +221,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -245,7 +245,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -269,7 +269,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -293,7 +293,7 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.3' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: From 5c95049cc7e2cc6f3d269f13e103700451c3c1e5 Mon Sep 17 00:00:00 2001 From: bfjelds Date: Mon, 29 Jun 2026 11:24:11 -0700 Subject: [PATCH 15/60] feat(acl): add opt-in GHCR fallback for ImageCustomizer and bump to 1.5 Add an opt-in fallback to convert-vhd-to-cosi.sh so that when the MCR ImageCustomizer image for the requested version is unavailable, the script can pull from a configurable fallback registry (IMG_CUSTOMIZER_CONTAINER_FALLBACK, e.g. ghcr.io/microsoft/imagecustomizer). The fallback is gated by a script param defaulting to off, normalizes major.minor versions to major.minor.0 for GHCR's full semver tags, and is enabled for the ACL COSI conversion step. Bump IMG_CUSTOMIZER_VERSION from 1.4 to 1.5 for ACL jobs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/.vsts-vhd-builder-release.yaml | 12 ++++-- .pipelines/.vsts-vhd-builder.yaml | 12 ++++-- .../templates/.builder-release-template.yaml | 1 + packer.mk | 2 +- .../scripts/convert-vhd-to-cosi.sh | 40 ++++++++++++++++++- 5 files changed, 56 insertions(+), 11 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index e863f0edeab..1d8fd37eceb 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -882,7 +882,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -909,7 +910,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -936,7 +938,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -963,7 +966,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: diff --git a/.pipelines/.vsts-vhd-builder.yaml b/.pipelines/.vsts-vhd-builder.yaml index 5f192d9d656..6deb0bccf60 100644 --- a/.pipelines/.vsts-vhd-builder.yaml +++ b/.pipelines/.vsts-vhd-builder.yaml @@ -221,7 +221,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -245,7 +246,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -269,7 +271,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -293,7 +296,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.4' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index 8473f5455f9..1ba01a0330c 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -413,6 +413,7 @@ steps: RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) STORAGE_ACCT_BLOB_URL: $(CLASSIC_BLOB) OFFER_NAME: $(OS_SKU) + IMG_CUSTOMIZER_ALLOW_FALLBACK: "true" - task: PublishPipelineArtifact@1 condition: | diff --git a/packer.mk b/packer.mk index dfb6f930833..28bcc3675c7 100755 --- a/packer.mk +++ b/packer.mk @@ -113,7 +113,7 @@ convert-sig-to-classic-storage-account-blob: az-login @./vhdbuilder/packer/convert-sig-to-classic-storage-account-blob.sh convert-vhd-to-cosi: az-login - @./vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh + @./vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh "$${IMG_CUSTOMIZER_ALLOW_FALLBACK:-false}" scanning-vhd: az-login @./vhdbuilder/packer/vhd-scanning.sh diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index d60515c93eb..47d36fe7960 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -19,6 +19,12 @@ do fi done +# Optional GHCR fallback: when the MCR ImageCustomizer image for the requested +# version is unavailable, optionally fall back to pulling the published GitHub +# Container Registry image (IMG_CUSTOMIZER_CONTAINER_FALLBACK). Gated by the +# first script argument and defaults to "false" so the fallback is opt-in. +ALLOW_GHCR_FALLBACK="${1:-false}" + WORK_DIR="$(pwd)/cosi-convert" mkdir -p "$WORK_DIR/build" "$WORK_DIR/out" @@ -59,14 +65,44 @@ if ! azcopy copy "$VHD_BLOB_URL" "$LOCAL_VHD" --recursive=true; then fi echo "Downloaded VHD to ${LOCAL_VHD}" -echo "Converting VHD to COSI using ImageCustomizer ${IMG_CUSTOMIZER_CONTAINER}:${IMG_CUSTOMIZER_VERSION}" +IMG_CUSTOMIZER_REF="${IMG_CUSTOMIZER_CONTAINER}:${IMG_CUSTOMIZER_VERSION}" + +echo "Pulling ImageCustomizer image ${IMG_CUSTOMIZER_REF}" +if ! docker pull "${IMG_CUSTOMIZER_REF}"; then + if [ "${ALLOW_GHCR_FALLBACK,,}" != "true" ]; then + echo "##vso[task.logissue type=error]Failed to pull ImageCustomizer image ${IMG_CUSTOMIZER_REF} and GHCR fallback is disabled" + exit 1 + fi + + if [ -z "${IMG_CUSTOMIZER_CONTAINER_FALLBACK:-}" ]; then + echo "##vso[task.logissue type=error]GHCR fallback is enabled but IMG_CUSTOMIZER_CONTAINER_FALLBACK is not set" + exit 1 + fi + + # GHCR only publishes fully-qualified semver tags (e.g. 1.5.0), whereas MCR + # exposes moving minor tags (e.g. 1.5). Normalize a major.minor version to + # major.minor.0 so the fallback tag resolves correctly. + FALLBACK_VERSION="${IMG_CUSTOMIZER_VERSION}" + if [[ "${FALLBACK_VERSION}" =~ ^[0-9]+\.[0-9]+$ ]]; then + FALLBACK_VERSION="${FALLBACK_VERSION}.0" + fi + + IMG_CUSTOMIZER_REF="${IMG_CUSTOMIZER_CONTAINER_FALLBACK}:${FALLBACK_VERSION}" + echo "MCR image unavailable; falling back to ${IMG_CUSTOMIZER_REF}" + if ! docker pull "${IMG_CUSTOMIZER_REF}"; then + echo "##vso[task.logissue type=error]Failed to pull ImageCustomizer fallback image ${IMG_CUSTOMIZER_REF}" + exit 1 + fi +fi + +echo "Converting VHD to COSI using ImageCustomizer ${IMG_CUSTOMIZER_REF}" docker run \ --rm \ --interactive \ --privileged=true \ -v "$WORK_DIR:/convert" \ -v /dev:/dev \ - "${IMG_CUSTOMIZER_CONTAINER}:${IMG_CUSTOMIZER_VERSION}" \ + "${IMG_CUSTOMIZER_REF}" \ convert \ --log-level "debug" \ --build-dir /convert/build \ From 00cd199179e6690573218bf0fdf8a461f3920147 Mon Sep 17 00:00:00 2001 From: bfjelds Date: Mon, 29 Jun 2026 11:42:53 -0700 Subject: [PATCH 16/60] refactor(acl): fold ImageCustomizer tag into container refs Include the tag directly in IMG_CUSTOMIZER_CONTAINER (mcr.microsoft.com/azurelinux/imagecustomizer:1.5) and IMG_CUSTOMIZER_CONTAINER_FALLBACK (ghcr.io/microsoft/imagecustomizer:1.5.0) for the ACL COSI conversion. Drop the separate IMG_CUSTOMIZER_VERSION variable and the major.minor to major.minor.0 normalization from convert-vhd-to-cosi.sh, since both registries now carry fully-qualified tags. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/.vsts-vhd-builder-release.yaml | 20 ++++++++----------- .pipelines/.vsts-vhd-builder.yaml | 20 ++++++++----------- .../scripts/convert-vhd-to-cosi.sh | 20 ++++++------------- 3 files changed, 22 insertions(+), 38 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index 1d8fd37eceb..e93cfef97a7 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -881,9 +881,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -909,9 +908,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -937,9 +935,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]True' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -965,9 +962,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]True' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: diff --git a/.pipelines/.vsts-vhd-builder.yaml b/.pipelines/.vsts-vhd-builder.yaml index 6deb0bccf60..49eff6b2a97 100644 --- a/.pipelines/.vsts-vhd-builder.yaml +++ b/.pipelines/.vsts-vhd-builder.yaml @@ -220,9 +220,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -245,9 +244,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -270,9 +268,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]True' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -295,9 +292,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]True' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_VERSION]1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index 47d36fe7960..5be87ed9e2a 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -8,7 +8,6 @@ required_env_vars=( "DESTINATION_STORAGE_CONTAINER" "CAPTURED_SIG_VERSION" "IMG_CUSTOMIZER_CONTAINER" - "IMG_CUSTOMIZER_VERSION" ) for v in "${required_env_vars[@]}" @@ -19,9 +18,10 @@ do fi done -# Optional GHCR fallback: when the MCR ImageCustomizer image for the requested -# version is unavailable, optionally fall back to pulling the published GitHub -# Container Registry image (IMG_CUSTOMIZER_CONTAINER_FALLBACK). Gated by the +# Optional GHCR fallback: when the MCR ImageCustomizer image +# (IMG_CUSTOMIZER_CONTAINER, including its tag) is unavailable, optionally fall +# back to pulling the published GitHub Container Registry image +# (IMG_CUSTOMIZER_CONTAINER_FALLBACK, also including its tag). Gated by the # first script argument and defaults to "false" so the fallback is opt-in. ALLOW_GHCR_FALLBACK="${1:-false}" @@ -65,7 +65,7 @@ if ! azcopy copy "$VHD_BLOB_URL" "$LOCAL_VHD" --recursive=true; then fi echo "Downloaded VHD to ${LOCAL_VHD}" -IMG_CUSTOMIZER_REF="${IMG_CUSTOMIZER_CONTAINER}:${IMG_CUSTOMIZER_VERSION}" +IMG_CUSTOMIZER_REF="${IMG_CUSTOMIZER_CONTAINER}" echo "Pulling ImageCustomizer image ${IMG_CUSTOMIZER_REF}" if ! docker pull "${IMG_CUSTOMIZER_REF}"; then @@ -79,15 +79,7 @@ if ! docker pull "${IMG_CUSTOMIZER_REF}"; then exit 1 fi - # GHCR only publishes fully-qualified semver tags (e.g. 1.5.0), whereas MCR - # exposes moving minor tags (e.g. 1.5). Normalize a major.minor version to - # major.minor.0 so the fallback tag resolves correctly. - FALLBACK_VERSION="${IMG_CUSTOMIZER_VERSION}" - if [[ "${FALLBACK_VERSION}" =~ ^[0-9]+\.[0-9]+$ ]]; then - FALLBACK_VERSION="${FALLBACK_VERSION}.0" - fi - - IMG_CUSTOMIZER_REF="${IMG_CUSTOMIZER_CONTAINER_FALLBACK}:${FALLBACK_VERSION}" + IMG_CUSTOMIZER_REF="${IMG_CUSTOMIZER_CONTAINER_FALLBACK}" echo "MCR image unavailable; falling back to ${IMG_CUSTOMIZER_REF}" if ! docker pull "${IMG_CUSTOMIZER_REF}"; then echo "##vso[task.logissue type=error]Failed to pull ImageCustomizer fallback image ${IMG_CUSTOMIZER_REF}" From 80801e2d37579690bb34b49ad4875ff190a70332 Mon Sep 17 00:00:00 2001 From: Adams Anaglo Date: Tue, 30 Jun 2026 17:24:20 -0400 Subject: [PATCH 17/60] acl(cosi): upload COSI directly to PMC via AFD under PMC's service connection - convert-vhd-to-cosi.sh: convert+stage only; emit AFD cosi_url + sha1 for aks-rp cosi register - upload-cosi-to-pmc.sh (new) + make target: upload staged COSI under PMC's SC (no az-login dep) - pipeline: Upload COSI to PMC task under COSI_PMC_SERVICE_CONNECTION; drop obsolete COSI immutable-copy step - fix azExitCode masking + set -u guards --- .../templates/.builder-release-template.yaml | 45 ++++++------- packer.mk | 3 + .../scripts/convert-vhd-to-cosi.sh | 65 ++++++++----------- .../scripts/upload-cosi-to-pmc.sh | 58 +++++++++++++++++ 4 files changed, 109 insertions(+), 62 deletions(-) create mode 100755 vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index 1ba01a0330c..b9307a21026 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -411,46 +411,41 @@ steps: displayName: Convert ACL VHD to COSI env: RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) - STORAGE_ACCT_BLOB_URL: $(CLASSIC_BLOB) OFFER_NAME: $(OS_SKU) IMG_CUSTOMIZER_ALLOW_FALLBACK: "true" + AFD_DOWNLOAD_HOSTNAME: $(AFD_DOWNLOAD_HOSTNAME) + COSI_CONTAINER: $(COSI_CONTAINER) - - task: PublishPipelineArtifact@1 + - task: AzureCLI@2 + inputs: + azureSubscription: $(COSI_PMC_SERVICE_CONNECTION) + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + make -f packer.mk upload-cosi-to-pmc condition: | and( succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), eq(variables['OS_SKU'], 'AzureContainerLinux') ) - displayName: Publish COSI Publishing Info - inputs: - artifactName: 'cosi-publishing-info-${{ parameters.artifactName }}' - targetPath: 'cosi-publishing-info.json' + displayName: Upload COSI to PMC + env: + AFD_UPLOAD_ENDPOINT: $(AFD_UPLOAD_ENDPOINT) + COSI_CONTAINER: $(COSI_CONTAINER) - - task: AzureCLI@2 - inputs: - azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) - scriptType: bash - scriptLocation: inlineScript - inlineScript: | - echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi to immutable storage container" - export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" - export AZCOPY_CONCURRENCY_VALUE="AUTO" - az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.cosi" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --auth-mode login || exit 1 - echo "Successfully copied COSI to immutable container" - azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --recursive=true + - task: PublishPipelineArtifact@1 condition: | and( succeeded(), - eq(variables.ENVIRONMENT, 'tme'), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux'), - or( - eq(variables.RUN_PREFETCH_OPTIMIZATION, 'False'), - eq(variables.PREFETCH_COMPATIBLE, 'False') - ) + eq(variables['OS_SKU'], 'AzureContainerLinux') ) - displayName: Copy COSI to Immutable Storage Container + displayName: Publish COSI Publishing Info + inputs: + artifactName: 'cosi-publishing-info-${{ parameters.artifactName }}' + targetPath: 'cosi-publishing-info.json' + - task: AzureCLI@2 inputs: diff --git a/packer.mk b/packer.mk index 28bcc3675c7..e128de30a6b 100755 --- a/packer.mk +++ b/packer.mk @@ -115,6 +115,9 @@ convert-sig-to-classic-storage-account-blob: az-login convert-vhd-to-cosi: az-login @./vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh "$${IMG_CUSTOMIZER_ALLOW_FALLBACK:-false}" +upload-cosi-to-pmc: + @./vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh + scanning-vhd: az-login @./vhdbuilder/packer/vhd-scanning.sh diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index 5be87ed9e2a..280d7ae1c0a 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -1,18 +1,20 @@ #!/bin/bash set -euo pipefail -# Converts an ACL VHD from blob storage to COSI format using ImageCustomizer, -# then uploads the COSI file back to blob storage. +# Converts an ACL VHD to COSI and stages it for the separate "Upload COSI to PMC" +# task (which runs under PMC's service connection). required_env_vars=( "DESTINATION_STORAGE_CONTAINER" "CAPTURED_SIG_VERSION" "IMG_CUSTOMIZER_CONTAINER" + "AFD_DOWNLOAD_HOSTNAME" + "COSI_CONTAINER" ) for v in "${required_env_vars[@]}" do - if [ -z "${!v}" ]; then + if [ -z "${!v:-}" ]; then echo "$v was not set!" exit 1 fi @@ -38,6 +40,9 @@ VHD_BLOB_URL="${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" LOCAL_VHD="$WORK_DIR/${CAPTURED_SIG_VERSION}.vhd" LOCAL_COSI="$WORK_DIR/out/${CAPTURED_SIG_VERSION}.cosi" +COSI_NAME="${CAPTURED_SIG_VERSION}.cosi" +COSI_DOWNLOAD_URL="https://${AFD_DOWNLOAD_HOSTNAME}/${COSI_CONTAINER}/${COSI_NAME}" + echo "Setting azcopy environment variables" export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" export AZCOPY_CONCURRENCY_VALUE="AUTO" @@ -47,7 +52,9 @@ mkdir -p "${AZCOPY_LOG_LOCATION}" mkdir -p "${AZCOPY_JOB_PLAN_LOCATION}" echo "Downloading VHD from ${VHD_BLOB_URL}" -if ! azcopy copy "$VHD_BLOB_URL" "$LOCAL_VHD" --recursive=true; then +if azcopy copy "$VHD_BLOB_URL" "$LOCAL_VHD" --recursive=true; then + echo "Downloaded VHD to ${LOCAL_VHD}" +else azExitCode=$? shopt -s nullglob for f in "${AZCOPY_LOG_LOCATION}"/*.log; do @@ -61,9 +68,8 @@ if ! azcopy copy "$VHD_BLOB_URL" "$LOCAL_VHD" --recursive=true; then done shopt -u nullglob echo "Failed to download VHD, exiting with code $azExitCode" - exit $azExitCode + exit "$azExitCode" fi -echo "Downloaded VHD to ${LOCAL_VHD}" IMG_CUSTOMIZER_REF="${IMG_CUSTOMIZER_CONTAINER}" @@ -107,36 +113,23 @@ if [ ! -f "$LOCAL_COSI" ]; then exit 1 fi -echo "Uploading COSI to ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" -if ! azcopy copy "$LOCAL_COSI" "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" --recursive=true; then - azExitCode=$? - shopt -s nullglob - for f in "${AZCOPY_LOG_LOCATION}"/*.log; do - echo "Azcopy log file: $f" - echo "##vso[build.uploadlog]$f" - if grep -q '"level":"Error"' "$f"; then - echo "log file $f contains errors" - echo "##vso[task.logissue type=error]Azcopy log file $f contains errors" - cat "$f" - fi - done - shopt -u nullglob - echo "Failed to upload COSI, exiting with code $azExitCode" - exit $azExitCode -fi - -echo "Successfully converted and uploaded COSI: ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.cosi" +# Move out of WORK_DIR (removed by the cleanup trap) so the upload task can find it. +STAGED_COSI="$(pwd)/${COSI_NAME}" +mv "$LOCAL_COSI" "$STAGED_COSI" +echo "Staged COSI for upload at ${STAGED_COSI}" -# Generate cosi-publishing-info.json for downstream consumption -COSI_SHA256=$(sha256sum "$LOCAL_COSI" | awk '{print $1}') -COSI_SIZE=$(stat -c%s "$LOCAL_COSI") +# cosi-publishing-info.json for aks-rp 'cosi register' (needs both sha256 and sha1). +COSI_SHA256=$(sha256sum "$STAGED_COSI" | awk '{print $1}') +COSI_SHA1=$(sha1sum "$STAGED_COSI" | awk '{print $1}') +COSI_SIZE=$(stat -c%s "$STAGED_COSI") -if [ -z "$IMAGE_VERSION" ]; then +if [ -z "${IMAGE_VERSION:-}" ]; then IMAGE_VERSION=$(date +%Y%m.%d.0) echo "IMAGE_VERSION was not set, defaulting to ${IMAGE_VERSION}" fi -if [ "${ARCHITECTURE,,}" = "arm64" ]; then +ARCH_LOWER="${ARCHITECTURE:-}" +if [ "${ARCH_LOWER,,}" = "arm64" ]; then IMAGE_ARCH="Arm64" else IMAGE_ARCH="x64" @@ -145,18 +138,16 @@ fi # OS_NAME is always Linux for ACL COSI artifacts OS_NAME="Linux" -COSI_NAME="${CAPTURED_SIG_VERSION}.cosi" -cosi_url="${STORAGE_ACCT_BLOB_URL}/${COSI_NAME}" - cat < cosi-publishing-info.json { - "cosi_url": "$cosi_url", + "cosi_url": "${COSI_DOWNLOAD_URL}", "sha256": "${COSI_SHA256}", + "sha1": "${COSI_SHA1}", "size_bytes": ${COSI_SIZE}, "os_name": "$OS_NAME", - "sku_name": "$SKU_NAME", - "offer_name": "$OFFER_NAME", - "hyperv_generation": "${HYPERV_GENERATION}", + "sku_name": "${SKU_NAME:-}", + "offer_name": "${OFFER_NAME:-}", + "hyperv_generation": "${HYPERV_GENERATION:-}", "image_architecture": "${IMAGE_ARCH}", "image_version": "${IMAGE_VERSION}" } diff --git a/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh b/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh new file mode 100755 index 00000000000..c38b024759b --- /dev/null +++ b/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh @@ -0,0 +1,58 @@ +#!/bin/bash +set -euo pipefail + +# Uploads the COSI staged by convert-vhd-to-cosi.sh to PMC's AFD upload endpoint, +# under PMC's service connection. No az-login dep (azcopy's data-plane token needs +# no active subscription, and 'az account set' would fail under PMC's identity). + +required_env_vars=( + "CAPTURED_SIG_VERSION" + "AFD_UPLOAD_ENDPOINT" + "COSI_CONTAINER" +) + +for v in "${required_env_vars[@]}" +do + if [ -z "${!v:-}" ]; then + echo "$v was not set!" + exit 1 + fi +done + +COSI_WORK_DIR="$(pwd)" +COSI_NAME="${CAPTURED_SIG_VERSION}.cosi" +STAGED_COSI="${COSI_WORK_DIR}/${COSI_NAME}" +COSI_UPLOAD_URL="${AFD_UPLOAD_ENDPOINT%/}/${COSI_CONTAINER}/${COSI_NAME}" + +if [ ! -f "$STAGED_COSI" ]; then + echo "##vso[task.logissue type=error]Staged COSI not found at ${STAGED_COSI}; the convert-vhd-to-cosi step must run first" + exit 1 +fi + +echo "Setting azcopy environment variables" +export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" +export AZCOPY_CONCURRENCY_VALUE="AUTO" +export AZCOPY_LOG_LOCATION="${COSI_WORK_DIR}/azcopy-cosi-upload-log-files/" +export AZCOPY_JOB_PLAN_LOCATION="${COSI_WORK_DIR}/azcopy-cosi-upload-job-plan-files/" +mkdir -p "${AZCOPY_LOG_LOCATION}" +mkdir -p "${AZCOPY_JOB_PLAN_LOCATION}" + +echo "Uploading COSI to ${COSI_UPLOAD_URL}" +if azcopy copy "$STAGED_COSI" "$COSI_UPLOAD_URL" --recursive=true; then + echo "Successfully uploaded COSI to ${COSI_UPLOAD_URL}" +else + azExitCode=$? + shopt -s nullglob + for f in "${AZCOPY_LOG_LOCATION}"/*.log; do + echo "Azcopy log file: $f" + echo "##vso[build.uploadlog]$f" + if grep -q '"level":"Error"' "$f"; then + echo "log file $f contains errors" + echo "##vso[task.logissue type=error]Azcopy log file $f contains errors" + cat "$f" + fi + done + shopt -u nullglob + echo "Failed to upload COSI, exiting with code $azExitCode" + exit "$azExitCode" +fi From 5de911aa4dd89709ca050610a67b7f19fb96c694 Mon Sep 17 00:00:00 2001 From: Adams Anaglo Date: Tue, 30 Jun 2026 18:29:40 -0400 Subject: [PATCH 18/60] acl(cosi): reference PMC service connection by literal name to satisfy ADO compile-time authz --- .pipelines/templates/.builder-release-template.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index b9307a21026..86e80d5a494 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -416,9 +416,12 @@ steps: AFD_DOWNLOAD_HOSTNAME: $(AFD_DOWNLOAD_HOSTNAME) COSI_CONTAINER: $(COSI_CONTAINER) + # Service connection referenced by literal name: $() macro refs fail ADO's + # compile-time service-connection authorization. Revert to a variable/parameter + # once a non-PoC, per-environment SC is wired. - task: AzureCLI@2 inputs: - azureSubscription: $(COSI_PMC_SERVICE_CONNECTION) + azureSubscription: aks-acl-cosi-tme-write-sc scriptType: bash scriptLocation: inlineScript inlineScript: | From 010dd65779e5804cb8044e591dff3fd402a1532c Mon Sep 17 00:00:00 2001 From: Adams Anaglo Date: Tue, 30 Jun 2026 21:54:41 -0400 Subject: [PATCH 19/60] Fix azcopy --- vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh b/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh index c38b024759b..8bacdfcbb5d 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh @@ -38,7 +38,7 @@ mkdir -p "${AZCOPY_LOG_LOCATION}" mkdir -p "${AZCOPY_JOB_PLAN_LOCATION}" echo "Uploading COSI to ${COSI_UPLOAD_URL}" -if azcopy copy "$STAGED_COSI" "$COSI_UPLOAD_URL" --recursive=true; then +if azcopy copy "$STAGED_COSI" "$COSI_UPLOAD_URL" --from-to=LocalBlob; then echo "Successfully uploaded COSI to ${COSI_UPLOAD_URL}" else azExitCode=$? From 25e34798cef7c70260b1d623820a6884e96c0338 Mon Sep 17 00:00:00 2001 From: Adams Anaglo Date: Wed, 1 Jul 2026 00:14:50 -0400 Subject: [PATCH 20/60] acl(cosi): upload COSI via Blob REST PUT with AAD token instead of azcopy azcopy cannot infer the service type for a custom *.azurefd.net host and won't send the AAD token to a non-default trusted suffix. Mint a storage-scoped token and PUT the staged .cosi directly to PMC's AFD upload endpoint (AFD passthrough-forwards the Bearer token to the blob origin), matching the proven Nebraska POC. Stream with curl -T to avoid loading a multi-GB COSI into memory. --- .../scripts/upload-cosi-to-pmc.sh | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh b/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh index 8bacdfcbb5d..4cbea514df6 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh @@ -1,9 +1,10 @@ #!/bin/bash set -euo pipefail -# Uploads the COSI staged by convert-vhd-to-cosi.sh to PMC's AFD upload endpoint, -# under PMC's service connection. No az-login dep (azcopy's data-plane token needs -# no active subscription, and 'az account set' would fail under PMC's identity). +# Uploads the COSI staged by convert-vhd-to-cosi.sh to PMC's AFD upload endpoint +# under PMC's service connection, via a direct Blob REST PUT authenticated with an +# AAD storage-scoped token. AFD forwards the Bearer token to the blob origin, so +# azcopy is not used (it cannot infer the service type for a custom *.azurefd.net host). required_env_vars=( "CAPTURED_SIG_VERSION" @@ -29,30 +30,23 @@ if [ ! -f "$STAGED_COSI" ]; then exit 1 fi -echo "Setting azcopy environment variables" -export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" -export AZCOPY_CONCURRENCY_VALUE="AUTO" -export AZCOPY_LOG_LOCATION="${COSI_WORK_DIR}/azcopy-cosi-upload-log-files/" -export AZCOPY_JOB_PLAN_LOCATION="${COSI_WORK_DIR}/azcopy-cosi-upload-job-plan-files/" -mkdir -p "${AZCOPY_LOG_LOCATION}" -mkdir -p "${AZCOPY_JOB_PLAN_LOCATION}" +RESP_FILE="${COSI_WORK_DIR}/cosi-afd-upload-response.txt" echo "Uploading COSI to ${COSI_UPLOAD_URL}" -if azcopy copy "$STAGED_COSI" "$COSI_UPLOAD_URL" --from-to=LocalBlob; then - echo "Successfully uploaded COSI to ${COSI_UPLOAD_URL}" +STORAGE_TOKEN="$(az account get-access-token --resource https://storage.azure.com --query accessToken -o tsv)" +HTTP_CODE="$(curl -sS -o "${RESP_FILE}" -w '%{http_code}' \ + -X PUT \ + -H "Authorization: Bearer ${STORAGE_TOKEN}" \ + -H "x-ms-blob-type: BlockBlob" \ + -H "x-ms-version: 2020-10-02" \ + -H "Content-Type: application/octet-stream" \ + -T "${STAGED_COSI}" \ + "${COSI_UPLOAD_URL}")" + +if [ "${HTTP_CODE}" = "201" ]; then + echo "Successfully uploaded COSI to ${COSI_UPLOAD_URL} (HTTP ${HTTP_CODE})" else - azExitCode=$? - shopt -s nullglob - for f in "${AZCOPY_LOG_LOCATION}"/*.log; do - echo "Azcopy log file: $f" - echo "##vso[build.uploadlog]$f" - if grep -q '"level":"Error"' "$f"; then - echo "log file $f contains errors" - echo "##vso[task.logissue type=error]Azcopy log file $f contains errors" - cat "$f" - fi - done - shopt -u nullglob - echo "Failed to upload COSI, exiting with code $azExitCode" - exit "$azExitCode" + echo "##vso[task.logissue type=error]COSI upload to ${COSI_UPLOAD_URL} failed with HTTP ${HTTP_CODE}" + cat "${RESP_FILE}" || true + exit 1 fi From d40bc9c78ad9cec542af754a24a1d6b66eb3b78c Mon Sep 17 00:00:00 2001 From: Adams Anaglo Date: Wed, 1 Jul 2026 10:00:47 -0400 Subject: [PATCH 21/60] acl(cosi): upload COSI via azblob SDK chunked block-blob upload A single Blob REST PUT is capped at 5000 MiB, so the real converted COSI (>5 GiB) failed with HTTP 413 RequestBodyTooLarge. Replace the curl PUT with a small cmd/cosi-upload Go tool that uses the Azure Blob SDK's UploadFile (16 MiB blocks, concurrency 8), which chunks via Put Block + Put Block List and is not subject to the single-PUT limit. Auth is unchanged (Microsoft Entra via the AzureCLI@2 login); AFD forwards the bearer token to the blob origin. azblob/azidentity are pinned to the versions already used by the e2e module. --- cmd/cosi-upload/main.go | 67 +++++++++++++++++++ go.mod | 12 +++- go.sum | 39 +++++++++-- packer.mk | 5 +- .../scripts/upload-cosi-to-pmc.sh | 35 ++++------ 5 files changed, 129 insertions(+), 29 deletions(-) create mode 100644 cmd/cosi-upload/main.go diff --git a/cmd/cosi-upload/main.go b/cmd/cosi-upload/main.go new file mode 100644 index 00000000000..872fce15eb4 --- /dev/null +++ b/cmd/cosi-upload/main.go @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +// Command cosi-upload streams a staged COSI artifact to PMC's Azure Front Door +// upload endpoint using the Azure Blob SDK's chunked block-blob upload. Because +// the SDK splits the file into blocks (Put Block + Put Block List), it is not +// subject to the single Put Blob size limit that a plain PUT hits on large COSI +// images. Authentication is Microsoft Entra ID via the Azure CLI login supplied +// by the pipeline's AzureCLI@2 task; the AFD endpoint transparently forwards the +// bearer token to the blob origin. +package main + +import ( + "context" + "flag" + "fmt" + "log" + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" +) + +func main() { + endpoint := flag.String("endpoint", "", "AFD upload endpoint base URL, e.g. https:// (required)") + container := flag.String("container", "", "destination container name (required)") + blob := flag.String("blob", "", "destination blob name (required)") + file := flag.String("file", "", "path to the local COSI file to upload (required)") + flag.Parse() + + if *endpoint == "" || *container == "" || *blob == "" || *file == "" { + log.Fatal("--endpoint, --container, --blob and --file are all required") + } + + if err := run(context.Background(), *endpoint, *container, *blob, *file); err != nil { + log.Fatalf("upload COSI: %v", err) + } +} + +func run(ctx context.Context, endpoint, container, blob, filePath string) error { + cred, err := azidentity.NewAzureCLICredential(nil) + if err != nil { + return fmt.Errorf("create Azure CLI credential: %w", err) + } + + client, err := azblob.NewClient(endpoint, cred, nil) + if err != nil { + return fmt.Errorf("create blob client for %s: %w", endpoint, err) + } + + f, err := os.Open(filePath) + if err != nil { + return fmt.Errorf("open %s: %w", filePath, err) + } + defer func() { _ = f.Close() }() + + log.Printf("Uploading %s to %s/%s/%s", filePath, endpoint, container, blob) + if _, err := client.UploadFile(ctx, container, blob, f, &azblob.UploadFileOptions{ + BlockSize: 16 * 1024 * 1024, // 16 MiB blocks + Concurrency: 8, + }); err != nil { + return fmt.Errorf("upload %s -> %s/%s/%s: %w", filePath, endpoint, container, blob, err) + } + + log.Printf("Successfully uploaded %s/%s/%s", endpoint, container, blob) + return nil +} diff --git a/go.mod b/go.mod index 8a3ae2e1a74..d45ac6a3807 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/Azure/agentbaker go 1.25.11 require ( + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 github.com/Azure/go-autorest/autorest/to v0.4.1 github.com/Masterminds/semver/v3 v3.5.0 github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df @@ -20,7 +22,10 @@ require ( ) require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect github.com/aws/aws-sdk-go-v2 v1.38.2 // indirect github.com/clarketm/json v1.17.1 // indirect github.com/coreos/go-json v0.0.0-20230131223807-18775e0fb4fb // indirect @@ -31,16 +36,19 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.3 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/nxadm/tail v1.4.8 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/spf13/pflag v1.0.9 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.51.0 // indirect golang.org/x/net v0.55.0 // indirect golang.org/x/sys v0.45.0 // indirect golang.org/x/text v0.37.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect ) diff --git a/go.sum b/go.sum index c03e757dbe5..f598801e317 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,31 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1/go.mod h1:Ng3urmn6dYe8gnbCMoHHVl5APYz2txho3koEkV2o2HA= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZYLBR2kBz5C8Tg0fw5w5Y7meRXWI= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc= github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/aws/aws-sdk-go-v2 v1.38.2 h1:QUkLO1aTW0yqW95pVzZS0LGFanL71hJ0a49w4TJLMyM= github.com/aws/aws-sdk-go-v2 v1.38.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df h1:GSoSVRLoBaFpOOds6QyY1L8AX7uoY+Ln3BHc22W40X0= github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df/go.mod h1:hiVxq5OP2bUGBRNS3Z/bt/reCLFNbdcST6gISi1fiOM= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clarketm/json v1.17.1 h1:U1IxjqJkJ7bRK4L6dyphmoO840P6bdhPdbbLySourqI= github.com/clarketm/json v1.17.1/go.mod h1:ynr2LRfb0fQU34l07csRNBTcivjySLLiY1YzQqKVfdo= github.com/coreos/butane v0.25.1 h1:Nm2WDRD7h3f6GUpazGlge1o417Z+eIC9bQlkpgVdNms= @@ -21,11 +39,12 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687 h1:uSmlDgJGbUB0bwQBcZomBTottKwEDF5fF8UjSwKSzWM= github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687/go.mod h1:Salmysdw7DAVuobBW/LwsKKgpyCPHUhjyJoMJD+ZJiI= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flatcar/ignition/v2 v2.0.0-20250903113522-05b8a773288c h1:MDbKEbAGtJuTZQ6Axnp3xEgh1nsJNW12rIIr0qexSGs= @@ -36,6 +55,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= +github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -48,6 +69,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -55,13 +78,14 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -73,11 +97,15 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.41.0 h1:OwKp4pXNgVxf6sCplzYo794OFNuoL2q2SBMU5NSWOjA= github.com/onsi/gomega v1.41.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= +github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -97,6 +125,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -118,6 +148,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/packer.mk b/packer.mk index e128de30a6b..c47c2d607ba 100755 --- a/packer.mk +++ b/packer.mk @@ -115,7 +115,10 @@ convert-sig-to-classic-storage-account-blob: az-login convert-vhd-to-cosi: az-login @./vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh "$${IMG_CUSTOMIZER_ALLOW_FALLBACK:-false}" -upload-cosi-to-pmc: +build-cosi-upload: setup-golang + @go build -o bin/cosi-upload ./cmd/cosi-upload + +upload-cosi-to-pmc: build-cosi-upload @./vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh scanning-vhd: az-login diff --git a/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh b/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh index 4cbea514df6..5431a485bdc 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh @@ -2,9 +2,10 @@ set -euo pipefail # Uploads the COSI staged by convert-vhd-to-cosi.sh to PMC's AFD upload endpoint -# under PMC's service connection, via a direct Blob REST PUT authenticated with an -# AAD storage-scoped token. AFD forwards the Bearer token to the blob origin, so -# azcopy is not used (it cannot infer the service type for a custom *.azurefd.net host). +# under PMC's service connection. Delegates the transfer to the cmd/cosi-upload Go +# tool, which uses the Azure Blob SDK's chunked block-blob upload (Put Block + Put +# Block List) so it is not subject to the single Put Blob size limit, and +# authenticates via the AzureCLI@2 task's Microsoft Entra login. required_env_vars=( "CAPTURED_SIG_VERSION" @@ -23,30 +24,20 @@ done COSI_WORK_DIR="$(pwd)" COSI_NAME="${CAPTURED_SIG_VERSION}.cosi" STAGED_COSI="${COSI_WORK_DIR}/${COSI_NAME}" -COSI_UPLOAD_URL="${AFD_UPLOAD_ENDPOINT%/}/${COSI_CONTAINER}/${COSI_NAME}" if [ ! -f "$STAGED_COSI" ]; then echo "##vso[task.logissue type=error]Staged COSI not found at ${STAGED_COSI}; the convert-vhd-to-cosi step must run first" exit 1 fi -RESP_FILE="${COSI_WORK_DIR}/cosi-afd-upload-response.txt" - -echo "Uploading COSI to ${COSI_UPLOAD_URL}" -STORAGE_TOKEN="$(az account get-access-token --resource https://storage.azure.com --query accessToken -o tsv)" -HTTP_CODE="$(curl -sS -o "${RESP_FILE}" -w '%{http_code}' \ - -X PUT \ - -H "Authorization: Bearer ${STORAGE_TOKEN}" \ - -H "x-ms-blob-type: BlockBlob" \ - -H "x-ms-version: 2020-10-02" \ - -H "Content-Type: application/octet-stream" \ - -T "${STAGED_COSI}" \ - "${COSI_UPLOAD_URL}")" - -if [ "${HTTP_CODE}" = "201" ]; then - echo "Successfully uploaded COSI to ${COSI_UPLOAD_URL} (HTTP ${HTTP_CODE})" -else - echo "##vso[task.logissue type=error]COSI upload to ${COSI_UPLOAD_URL} failed with HTTP ${HTTP_CODE}" - cat "${RESP_FILE}" || true +UPLOADER="${COSI_WORK_DIR}/bin/cosi-upload" +if [ ! -x "$UPLOADER" ]; then + echo "##vso[task.logissue type=error]cosi-upload binary not found at ${UPLOADER}; build it with 'go build -o bin/cosi-upload ./cmd/cosi-upload'" exit 1 fi + +"$UPLOADER" \ + --endpoint "${AFD_UPLOAD_ENDPOINT%/}" \ + --container "${COSI_CONTAINER}" \ + --blob "${COSI_NAME}" \ + --file "${STAGED_COSI}" From cd7181d663b66ac4b9ef63d472b51beade6239c4 Mon Sep 17 00:00:00 2001 From: Adams Anaglo Date: Mon, 6 Jul 2026 12:11:20 -0400 Subject: [PATCH 22/60] Reference service connection variable --- .pipelines/templates/.builder-release-template.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index 86e80d5a494..b9307a21026 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -416,12 +416,9 @@ steps: AFD_DOWNLOAD_HOSTNAME: $(AFD_DOWNLOAD_HOSTNAME) COSI_CONTAINER: $(COSI_CONTAINER) - # Service connection referenced by literal name: $() macro refs fail ADO's - # compile-time service-connection authorization. Revert to a variable/parameter - # once a non-PoC, per-environment SC is wired. - task: AzureCLI@2 inputs: - azureSubscription: aks-acl-cosi-tme-write-sc + azureSubscription: $(COSI_PMC_SERVICE_CONNECTION) scriptType: bash scriptLocation: inlineScript inlineScript: | From 38d211690d5cdf35ac33d107f0a1d8bf432a4df5 Mon Sep 17 00:00:00 2001 From: "aks-node-assistant[bot]" <190555641+aks-node-assistant[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:53:02 +0000 Subject: [PATCH 23/60] chore: tidy aks-node-controller/go.sum --- aks-node-controller/go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aks-node-controller/go.sum b/aks-node-controller/go.sum index c22395dce00..ee0e0d24b8b 100644 --- a/aks-node-controller/go.sum +++ b/aks-node-controller/go.sum @@ -64,8 +64,8 @@ golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From 1facc1a7f94070cfa27b1da96b153546ea9fa451 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:41:53 -0400 Subject: [PATCH 24/60] Use QEMU to support arm64 kernel cmdline extractions from UKI --- .../scripts/convert-vhd-to-cosi.sh | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index 280d7ae1c0a..5608fc9fae8 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -71,10 +71,38 @@ else exit "$azExitCode" fi +# Determine the target architecture once; reused below for the ImageCustomizer +# container platform and for the publishing-info image_architecture field. +ARCH_LOWER="${ARCHITECTURE:-}" + +# Match the ImageCustomizer container's architecture to the VHD being converted. +# ImageCustomizer reads the kernel cmdline from the image's UKI using objcopy, +# which only understands its own architecture: an x86_64 objcopy cannot parse an +# aarch64 UKI ("file format not recognized"), so COSI conversion fails for ARM64 +# ACL VHDs on an x86_64 build agent. Running the arch-matched image (under +# QEMU/binfmt emulation when the agent differs) gives objcopy the matching +# target. Defaults to amd64 when ARCHITECTURE is unset. +if [ "${ARCH_LOWER,,}" = "arm64" ]; then + IMG_CUSTOMIZER_PLATFORM="linux/arm64" +else + IMG_CUSTOMIZER_PLATFORM="linux/amd64" +fi + +# Cross-architecture emulation needs binfmt_misc QEMU handlers on the build +# agent. Fail fast with an actionable message instead of a cryptic "exec format +# error" from the container runtime. +HOST_ARCH="$(uname -m)" +if [ "$IMG_CUSTOMIZER_PLATFORM" = "linux/arm64" ] && [ "$HOST_ARCH" != "aarch64" ] && [ "$HOST_ARCH" != "arm64" ]; then + if [ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]; then + echo "##vso[task.logissue type=error]ARM64 COSI conversion requires QEMU aarch64 emulation (binfmt_misc qemu-aarch64) on the build agent, but it is not registered" + exit 1 + fi +fi + IMG_CUSTOMIZER_REF="${IMG_CUSTOMIZER_CONTAINER}" -echo "Pulling ImageCustomizer image ${IMG_CUSTOMIZER_REF}" -if ! docker pull "${IMG_CUSTOMIZER_REF}"; then +echo "Pulling ImageCustomizer image ${IMG_CUSTOMIZER_REF} for platform ${IMG_CUSTOMIZER_PLATFORM}" +if ! docker pull --platform "${IMG_CUSTOMIZER_PLATFORM}" "${IMG_CUSTOMIZER_REF}"; then if [ "${ALLOW_GHCR_FALLBACK,,}" != "true" ]; then echo "##vso[task.logissue type=error]Failed to pull ImageCustomizer image ${IMG_CUSTOMIZER_REF} and GHCR fallback is disabled" exit 1 @@ -87,14 +115,15 @@ if ! docker pull "${IMG_CUSTOMIZER_REF}"; then IMG_CUSTOMIZER_REF="${IMG_CUSTOMIZER_CONTAINER_FALLBACK}" echo "MCR image unavailable; falling back to ${IMG_CUSTOMIZER_REF}" - if ! docker pull "${IMG_CUSTOMIZER_REF}"; then + if ! docker pull --platform "${IMG_CUSTOMIZER_PLATFORM}" "${IMG_CUSTOMIZER_REF}"; then echo "##vso[task.logissue type=error]Failed to pull ImageCustomizer fallback image ${IMG_CUSTOMIZER_REF}" exit 1 fi fi -echo "Converting VHD to COSI using ImageCustomizer ${IMG_CUSTOMIZER_REF}" +echo "Converting VHD to COSI using ImageCustomizer ${IMG_CUSTOMIZER_REF} (${IMG_CUSTOMIZER_PLATFORM})" docker run \ + --platform "${IMG_CUSTOMIZER_PLATFORM}" \ --rm \ --interactive \ --privileged=true \ @@ -128,7 +157,7 @@ if [ -z "${IMAGE_VERSION:-}" ]; then echo "IMAGE_VERSION was not set, defaulting to ${IMAGE_VERSION}" fi -ARCH_LOWER="${ARCHITECTURE:-}" +# ARCH_LOWER is derived once near the top of the script and reused here. if [ "${ARCH_LOWER,,}" = "arm64" ]; then IMAGE_ARCH="Arm64" else From c1ea9fc5ff17c2453f89896b68d73ab329a88cdf Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:28:18 -0400 Subject: [PATCH 25/60] QEMU --- .../scripts/convert-vhd-to-cosi.sh | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index 5608fc9fae8..a809134a094 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -71,30 +71,26 @@ else exit "$azExitCode" fi -# Determine the target architecture once; reused below for the ImageCustomizer -# container platform and for the publishing-info image_architecture field. +# Run the arch-matched ImageCustomizer image so its objcopy can read the VHD's +# UKI kernel cmdline. ARCH_LOWER is reused for image_architecture below. ARCH_LOWER="${ARCHITECTURE:-}" - -# Match the ImageCustomizer container's architecture to the VHD being converted. -# ImageCustomizer reads the kernel cmdline from the image's UKI using objcopy, -# which only understands its own architecture: an x86_64 objcopy cannot parse an -# aarch64 UKI ("file format not recognized"), so COSI conversion fails for ARM64 -# ACL VHDs on an x86_64 build agent. Running the arch-matched image (under -# QEMU/binfmt emulation when the agent differs) gives objcopy the matching -# target. Defaults to amd64 when ARCHITECTURE is unset. if [ "${ARCH_LOWER,,}" = "arm64" ]; then IMG_CUSTOMIZER_PLATFORM="linux/arm64" else IMG_CUSTOMIZER_PLATFORM="linux/amd64" fi -# Cross-architecture emulation needs binfmt_misc QEMU handlers on the build -# agent. Fail fast with an actionable message instead of a cryptic "exec format -# error" from the container runtime. +# Converting an arm64 image on an x86_64 agent needs the qemu-aarch64 binfmt +# handler; register it (idempotent). No-op on native arm64 or x86_64 builds. +QEMU_BINFMT_IMAGE="tonistiigi/binfmt:qemu-v10.2.3" HOST_ARCH="$(uname -m)" if [ "$IMG_CUSTOMIZER_PLATFORM" = "linux/arm64" ] && [ "$HOST_ARCH" != "aarch64" ] && [ "$HOST_ARCH" != "arm64" ]; then if [ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]; then - echo "##vso[task.logissue type=error]ARM64 COSI conversion requires QEMU aarch64 emulation (binfmt_misc qemu-aarch64) on the build agent, but it is not registered" + echo "Registering QEMU aarch64 emulation via ${QEMU_BINFMT_IMAGE}" + docker run --privileged --rm "${QEMU_BINFMT_IMAGE}" --install arm64 || true + fi + if [ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]; then + echo "##vso[task.logissue type=error]Failed to register qemu-aarch64 emulation; cannot run the arm64 ImageCustomizer container" exit 1 fi fi From c5c1482ced8a8e8ecf47414b8baa7eb798b24316 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:05:53 -0400 Subject: [PATCH 26/60] Fix mounting --- .../packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index a809134a094..f5aef2d7e7d 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -117,6 +117,9 @@ if ! docker pull --platform "${IMG_CUSTOMIZER_PLATFORM}" "${IMG_CUSTOMIZER_REF}" fi fi +# No -v /dev:/dev: IC 1.5's entrypoint mounts its own devtmpfs on /dev, so bind- +# mounting the host's fails "devtmpfs already mounted on /dev" (exit 32) under +# QEMU. -v /dev:/dev is only for IC v1.4 and below; v1.5+ uses --privileged alone. echo "Converting VHD to COSI using ImageCustomizer ${IMG_CUSTOMIZER_REF} (${IMG_CUSTOMIZER_PLATFORM})" docker run \ --platform "${IMG_CUSTOMIZER_PLATFORM}" \ @@ -124,7 +127,6 @@ docker run \ --interactive \ --privileged=true \ -v "$WORK_DIR:/convert" \ - -v /dev:/dev \ "${IMG_CUSTOMIZER_REF}" \ convert \ --log-level "debug" \ From 9de86fec796152d17f297a0f4990225267c215f9 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:42:29 -0400 Subject: [PATCH 27/60] Use native arm64 pool --- .pipelines/.vsts-vhd-builder-release.yaml | 64 +++++++++++++ .../.builder-cosi-convert-template.yaml | 93 +++++++++++++++++++ .../templates/.builder-release-template.yaml | 23 ++++- .../scripts/convert-vhd-to-cosi.sh | 19 +--- 4 files changed, 180 insertions(+), 19 deletions(-) create mode 100644 .pipelines/templates/.builder-cosi-convert-template.yaml diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index 2c970591317..49d78686018 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -919,6 +919,70 @@ stages: useOverrides: ${{ parameters.useOverrides }} overrideBranch: ${{ parameters.overrideBranch }} artifactName: acl-arm64-fips-tl-gen2 + # Native-arm64 COSI conversion jobs. The arm64 ACL build jobs above stage the + # VHD in blob storage and skip COSI (ImageCustomizer's btrfs subvolume ioctls + # don't work under qemu emulation on the x86_64 build pool). These jobs run on + # the native arm64 pool to convert the staged VHD and publish the COSI. + - job: convertaclarm64tlgen2 + displayName: Convert ACL ARM64 TL Gen2 VHD to COSI (native arm64) + condition: and(succeeded(), eq('${{ parameters.buildaclarm64tlgen2 }}', true)) + dependsOn: buildaclarm64tlgen2 + timeoutInMinutes: 120 + pool: + name: $(NODE_SIG_TME_POOL_ARM64_NAME) + variables: + CAPTURED_SIG_VERSION: $[ dependencies.buildaclarm64tlgen2.outputs['exportCapturedSigVersion.CAPTURED_SIG_VERSION'] ] + steps: + - bash: | + echo '##vso[task.setvariable variable=OS_SKU]AzureContainerLinux' + echo '##vso[task.setvariable variable=OS_VERSION]acl' + echo '##vso[task.setvariable variable=IMG_PUBLISHER]MicrosoftCBLMariner' + echo '##vso[task.setvariable variable=IMG_OFFER]azure-linux-3' + echo '##vso[task.setvariable variable=IMG_SKU]azure-linux-3-arm64-gen2-acl' + echo '##vso[task.setvariable variable=IMG_VERSION]3.20260616.01' + echo '##vso[task.setvariable variable=HYPERV_GENERATION]V2' + echo '##vso[task.setvariable variable=AZURE_VM_SIZE]Standard_D16pds_v6' + echo '##vso[task.setvariable variable=FEATURE_FLAGS]None' + echo '##vso[task.setvariable variable=ARCHITECTURE]ARM64' + echo '##vso[task.setvariable variable=ENABLE_FIPS]false' + echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' + echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' + displayName: Setup Build Variables + - template: ./templates/.builder-cosi-convert-template.yaml + parameters: + artifactName: acl-arm64-tl-gen2 + - job: convertaclarm64fipstlgen2 + displayName: Convert ACL ARM64 FIPS TL Gen2 VHD to COSI (native arm64) + condition: and(succeeded(), eq('${{ parameters.buildaclarm64fipstlgen2 }}', true)) + dependsOn: buildaclarm64fipstlgen2 + timeoutInMinutes: 120 + pool: + name: $(NODE_SIG_TME_POOL_ARM64_NAME) + variables: + CAPTURED_SIG_VERSION: $[ dependencies.buildaclarm64fipstlgen2.outputs['exportCapturedSigVersion.CAPTURED_SIG_VERSION'] ] + steps: + - bash: | + echo '##vso[task.setvariable variable=OS_SKU]AzureContainerLinux' + echo '##vso[task.setvariable variable=OS_VERSION]acl' + echo '##vso[task.setvariable variable=IMG_PUBLISHER]MicrosoftCBLMariner' + echo '##vso[task.setvariable variable=IMG_OFFER]azure-linux-3' + echo '##vso[task.setvariable variable=IMG_SKU]azure-linux-3-arm64-gen2-acl' + echo '##vso[task.setvariable variable=IMG_VERSION]3.20260616.01' + echo '##vso[task.setvariable variable=HYPERV_GENERATION]V2' + echo '##vso[task.setvariable variable=AZURE_VM_SIZE]Standard_D16pds_v6' + echo '##vso[task.setvariable variable=FEATURE_FLAGS]None' + echo '##vso[task.setvariable variable=ARCHITECTURE]ARM64' + echo '##vso[task.setvariable variable=ENABLE_FIPS]True' + echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' + echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' + displayName: Setup Build Variables + - template: ./templates/.builder-cosi-convert-template.yaml + parameters: + artifactName: acl-arm64-fips-tl-gen2 - job: build2404arm64gbgen2containerd condition: eq('${{ parameters.build2404arm64gbgen2containerd }}', true) dependsOn: [ ] diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml new file mode 100644 index 00000000000..65634e724ee --- /dev/null +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -0,0 +1,93 @@ +# Steps for the native-arm64 ACL COSI conversion job. +# +# ACL COSI conversion runs ImageCustomizer, which mounts the image's btrfs rootfs +# and enumerates subvolumes. Those btrfs ioctls are not supported under qemu-user +# emulation, so the arm64 conversion cannot run on the x86_64 build pool. Instead, +# the x86_64 build job stages the VHD in blob storage and skips COSI, and this +# template runs on the native arm64 pool to download that staged VHD, convert it, +# upload the COSI to PMC, publish the info artifact, and copy the VHD to immutable +# storage (mirroring what the build job does inline for x64 ACL). +# +# The consuming job must set the ACL build variables (OS_SKU, OS_VERSION, +# HYPERV_GENERATION, ARCHITECTURE, IMG_CUSTOMIZER_CONTAINER, etc.) and provide +# CAPTURED_SIG_VERSION (from the build job's exportCapturedSigVersion output). +parameters: + - name: artifactName + type: string + +steps: + - bash: | + SKU_NAME=${OS_VERSION} && \ + if [[ "${HYPERV_GENERATION,,}" == "v2" ]]; then SKU_NAME="${SKU_NAME}gen2"; fi && \ + if [[ "${ARCHITECTURE,,}" == "arm64" ]]; then SKU_NAME="${SKU_NAME}arm64"; fi && \ + if grep -q "NVIDIA_GB" <<< "$FEATURE_FLAGS"; then SKU_NAME="${SKU_NAME}gb"; fi && \ + if [[ "${ENABLE_FIPS,,}" == "true" ]]; then SKU_NAME="${SKU_NAME}fips"; fi && \ + if grep -q "cvm" <<< "$FEATURE_FLAGS"; then SKU_NAME="${SKU_NAME}CVM"; fi && \ + if [[ "${IMG_SKU}" == *"minimal"* ]]; then SKU_NAME="${SKU_NAME}minimal"; fi && \ + if [[ "${ENABLE_TRUSTED_LAUNCH}" == "True" ]]; then SKU_NAME="${SKU_NAME}TL"; fi && \ + SKU_NAME=$(echo ${SKU_NAME} | tr -d '.') && \ + echo "##vso[task.setvariable variable=SKU_NAME]$SKU_NAME" + echo "Set SKU_NAME to $SKU_NAME" + displayName: Set SKU Name + + - bash: | + echo "##vso[task.setvariable variable=DESTINATION_STORAGE_CONTAINER]${CLASSIC_BLOB_STAGING}" + echo "DESTINATION_STORAGE_CONTAINER is set to ${CLASSIC_BLOB_STAGING}" + displayName: Set Destination Storage Container to Staging + + - task: AzureCLI@2 + inputs: + azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + make -f packer.mk convert-vhd-to-cosi + condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True')) + displayName: Convert ACL VHD to COSI + env: + RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) + OFFER_NAME: $(OS_SKU) + IMG_CUSTOMIZER_ALLOW_FALLBACK: "true" + AFD_DOWNLOAD_HOSTNAME: $(AFD_DOWNLOAD_HOSTNAME) + COSI_CONTAINER: $(COSI_CONTAINER) + + - task: AzureCLI@2 + inputs: + azureSubscription: $(COSI_PMC_SERVICE_CONNECTION) + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + make -f packer.mk upload-cosi-to-pmc + condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True')) + displayName: Upload COSI to PMC + env: + AFD_UPLOAD_ENDPOINT: $(AFD_UPLOAD_ENDPOINT) + COSI_CONTAINER: $(COSI_CONTAINER) + + - task: PublishPipelineArtifact@1 + condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True')) + displayName: Publish COSI Publishing Info + inputs: + artifactName: 'cosi-publishing-info-${{ parameters.artifactName }}' + targetPath: 'cosi-publishing-info.json' + + - task: AzureCLI@2 + inputs: + azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd to immutable storage container" + export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" + export AZCOPY_CONCURRENCY_VALUE="AUTO" + az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.vhd" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --auth-mode login || exit 1 + echo "Successfully copied to immutable container" + # Remove old VHD from staging container after copy to immutable container is complete + azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --recursive=true + condition: | + and( + succeeded(), + eq(variables.ENVIRONMENT, 'tme'), + eq(variables.GENERATE_PUBLISHING_INFO, 'True') + ) + displayName: Copy to Immutable Storage Container diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index 99f6095ef71..7166e5e32af 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -163,6 +163,15 @@ steps: condition: always() displayName: Set Post-Build Variables + # Expose the captured SIG version to the separate native-arm64 COSI convert job + # (arm64 ACL COSI conversion can't run under emulation, so it runs on the arm64 + # pool as its own job that consumes this output variable). + - bash: | + echo "##vso[task.setvariable variable=CAPTURED_SIG_VERSION;isOutput=true]${CAPTURED_SIG_VERSION}" + name: exportCapturedSigVersion + displayName: Export Captured SIG Version for arm64 COSI convert + condition: and(succeeded(), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) + - bash: | set -euo pipefail if [ "${BUILDER:-}" = "imagecustomizer" ]; then @@ -404,11 +413,14 @@ steps: scriptLocation: inlineScript inlineScript: | make -f packer.mk convert-vhd-to-cosi + # arm64 ACL runs this on the native arm64 pool as a separate job (see + # convertaclarm64* jobs); it can't run under emulation here. condition: | and( succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux') + eq(variables['OS_SKU'], 'AzureContainerLinux'), + ne(variables['ARCHITECTURE'], 'ARM64') ) displayName: Convert ACL VHD to COSI env: @@ -429,7 +441,8 @@ steps: and( succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux') + eq(variables['OS_SKU'], 'AzureContainerLinux'), + ne(variables['ARCHITECTURE'], 'ARM64') ) displayName: Upload COSI to PMC env: @@ -441,7 +454,8 @@ steps: and( succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux') + eq(variables['OS_SKU'], 'AzureContainerLinux'), + ne(variables['ARCHITECTURE'], 'ARM64') ) displayName: Publish COSI Publishing Info inputs: @@ -462,11 +476,14 @@ steps: echo "Successfully copied to immutable container" # Remove old VHD from staging container after copy to immutable container is complete azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --recursive=true + # arm64 ACL keeps its staged VHD for the separate native-arm64 COSI convert + # job to download; that job performs the immutable copy after conversion. condition: | and( succeeded(), eq(variables.ENVIRONMENT, 'tme'), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), + not(and(eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64'))), or( eq(variables.RUN_PREFETCH_OPTIMIZATION, 'False'), eq(variables.PREFETCH_COMPATIBLE, 'False') diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index f5aef2d7e7d..a0e9c49b2ec 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -72,7 +72,9 @@ else fi # Run the arch-matched ImageCustomizer image so its objcopy can read the VHD's -# UKI kernel cmdline. ARCH_LOWER is reused for image_architecture below. +# UKI kernel cmdline. ARCH_LOWER is reused for image_architecture below. This +# convert job always runs on an agent whose host arch matches ARCHITECTURE (arm64 +# ACL converts run on a native arm64 pool via a separate job); no emulation. ARCH_LOWER="${ARCHITECTURE:-}" if [ "${ARCH_LOWER,,}" = "arm64" ]; then IMG_CUSTOMIZER_PLATFORM="linux/arm64" @@ -80,21 +82,6 @@ else IMG_CUSTOMIZER_PLATFORM="linux/amd64" fi -# Converting an arm64 image on an x86_64 agent needs the qemu-aarch64 binfmt -# handler; register it (idempotent). No-op on native arm64 or x86_64 builds. -QEMU_BINFMT_IMAGE="tonistiigi/binfmt:qemu-v10.2.3" -HOST_ARCH="$(uname -m)" -if [ "$IMG_CUSTOMIZER_PLATFORM" = "linux/arm64" ] && [ "$HOST_ARCH" != "aarch64" ] && [ "$HOST_ARCH" != "arm64" ]; then - if [ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]; then - echo "Registering QEMU aarch64 emulation via ${QEMU_BINFMT_IMAGE}" - docker run --privileged --rm "${QEMU_BINFMT_IMAGE}" --install arm64 || true - fi - if [ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]; then - echo "##vso[task.logissue type=error]Failed to register qemu-aarch64 emulation; cannot run the arm64 ImageCustomizer container" - exit 1 - fi -fi - IMG_CUSTOMIZER_REF="${IMG_CUSTOMIZER_CONTAINER}" echo "Pulling ImageCustomizer image ${IMG_CUSTOMIZER_REF} for platform ${IMG_CUSTOMIZER_PLATFORM}" From 26f6416c21a67793e21cc5a45ee585494dfad99a Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Tue, 21 Jul 2026 17:03:24 -0400 Subject: [PATCH 28/60] arm64 COSI: bundle source + bootstrap tooling on minimal pool The arm64 1ES pool image lacks git and other build tooling. Work around without pool changes: - Bundle AgentBaker source as a pipeline artifact from the arm64 ACL build jobs (top of shared template, gated to arm64 ACL only). - Convert template: skip checkout: self, download the source bundle, and bootstrap docker / az CLI / azcopy / make / Go on first run (idempotent, apt/tdnf/dnf aware). --- .pipelines/.vsts-vhd-builder-release.yaml | 2 + .../.builder-cosi-convert-template.yaml | 114 ++++++++++++++++++ .../templates/.builder-release-template.yaml | 26 ++++ 3 files changed, 142 insertions(+) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index 49d78686018..bd8a511934c 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -953,6 +953,7 @@ stages: - template: ./templates/.builder-cosi-convert-template.yaml parameters: artifactName: acl-arm64-tl-gen2 + buildJobName: buildaclarm64tlgen2 - job: convertaclarm64fipstlgen2 displayName: Convert ACL ARM64 FIPS TL Gen2 VHD to COSI (native arm64) condition: and(succeeded(), eq('${{ parameters.buildaclarm64fipstlgen2 }}', true)) @@ -983,6 +984,7 @@ stages: - template: ./templates/.builder-cosi-convert-template.yaml parameters: artifactName: acl-arm64-fips-tl-gen2 + buildJobName: buildaclarm64fipstlgen2 - job: build2404arm64gbgen2containerd condition: eq('${{ parameters.build2404arm64gbgen2containerd }}', true) dependsOn: [ ] diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index 65634e724ee..15f2f0673a2 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -11,11 +11,125 @@ # The consuming job must set the ACL build variables (OS_SKU, OS_VERSION, # HYPERV_GENERATION, ARCHITECTURE, IMG_CUSTOMIZER_CONTAINER, etc.) and provide # CAPTURED_SIG_VERSION (from the build job's exportCapturedSigVersion output). +# +# The arm64 pool has no git, so we skip the built-in `checkout: self` and hydrate +# the workspace from a source bundle the build job publishes. parameters: - name: artifactName type: string + - name: buildJobName + type: string steps: + - checkout: none + + # The arm64 pool image is minimal, so bootstrap every tool the convert flow + # needs: docker (privileged), az CLI, azcopy, make/tar/curl, and Go. Idempotent + # via `command -v` guards, so it's a no-op on any tool that's already present. + - bash: | + set -euo pipefail + echo "==== Bootstrapping tooling on $(uname -a) ====" + + if command -v apt-get >/dev/null 2>&1; then + PKG=apt + elif command -v tdnf >/dev/null 2>&1; then + PKG=tdnf + elif command -v dnf >/dev/null 2>&1; then + PKG=dnf + else + echo "##vso[task.logissue type=error]No supported package manager (apt/tdnf/dnf) on this agent" + exit 1 + fi + echo "Package manager: $PKG" + + sudo -n true >/dev/null 2>&1 || { + echo "##vso[task.logissue type=error]This agent user does not have passwordless sudo; cannot install tools" + exit 1 + } + + pkg_install() { + case "$PKG" in + apt) sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "$@" ;; + tdnf) sudo tdnf install -y "$@" ;; + dnf) sudo dnf install -y "$@" ;; + esac + } + + if [ "$PKG" = apt ]; then + sudo DEBIAN_FRONTEND=noninteractive apt-get update -y + fi + + # Baseline utilities used by scripts and downloads below. + pkg_install ca-certificates curl tar gnupg make jq + + # git isn't used by the convert scripts, but install it so any implicit + # agent-side cleanup checkouts don't tank the job. + if ! command -v git >/dev/null 2>&1; then + pkg_install git + fi + + # Docker (must support --privileged for ImageCustomizer's loopback mounts). + if ! command -v docker >/dev/null 2>&1; then + case "$PKG" in + apt) pkg_install docker.io ;; + tdnf|dnf) pkg_install moby-engine moby-cli || pkg_install docker ;; + esac + fi + sudo systemctl start docker 2>/dev/null || sudo service docker start 2>/dev/null || true + # Give the agent user access to the docker socket for the duration of the job. + sudo chmod 666 /var/run/docker.sock 2>/dev/null || true + docker version + + # Azure CLI. + if ! command -v az >/dev/null 2>&1; then + case "$PKG" in + apt) curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash ;; + tdnf|dnf) pkg_install azure-cli ;; + esac + fi + az version + + # azcopy static binary. + if ! command -v azcopy >/dev/null 2>&1; then + case "$(uname -m)" in + aarch64|arm64) AZCOPY_URL=https://aka.ms/downloadazcopy-v10-linux-arm64 ;; + *) AZCOPY_URL=https://aka.ms/downloadazcopy-v10-linux ;; + esac + tmp="$(mktemp -d)" + curl -sSL "$AZCOPY_URL" -o "$tmp/azcopy.tgz" + tar -xzf "$tmp/azcopy.tgz" -C "$tmp" + # extracted dir is like azcopy_linux_arm64_10.x.x/ + azcopy_bin="$(find "$tmp" -maxdepth 2 -type f -name azcopy | head -n1)" + sudo install -m 0755 "$azcopy_bin" /usr/local/bin/azcopy + rm -rf "$tmp" + fi + azcopy --version + + # Go: on Ubuntu, hack/setup_golang.sh will install Microsoft Go via apt. + # On tdnf/dnf hosts it expects Go to already be on PATH, so install it here. + if ! command -v go >/dev/null 2>&1; then + case "$PKG" in + tdnf|dnf) pkg_install golang ;; + esac + fi + go version || echo "Go will be installed by hack/setup_golang.sh" + + echo "==== Tooling bootstrap complete ====" + displayName: Bootstrap arm64 convert tooling + + - task: DownloadPipelineArtifact@2 + displayName: Download AgentBaker source bundle + inputs: + artifact: agentbaker-sources-${{ parameters.buildJobName }} + path: $(Pipeline.Workspace)/agentbaker-sources + + - bash: | + set -euo pipefail + mkdir -p "$(Build.SourcesDirectory)" + tar -xzf "$(Pipeline.Workspace)/agentbaker-sources/agentbaker-sources.tar.gz" -C "$(Build.SourcesDirectory)" + ls "$(Build.SourcesDirectory)" + displayName: Extract AgentBaker source bundle + - bash: | SKU_NAME=${OS_VERSION} && \ if [[ "${HYPERV_GENERATION,,}" == "v2" ]]; then SKU_NAME="${SKU_NAME}gen2"; fi && \ diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index 7166e5e32af..c8a17845e65 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -10,6 +10,32 @@ parameters: default: master steps: + # For arm64 ACL: publish a source bundle for the separate native-arm64 COSI + # convert job. The arm64 pool has no git so `checkout: self` fails there; the + # convert job downloads this bundle instead of cloning. Runs first (before any + # downloads) so the bundle stays small. + - bash: | + set -euo pipefail + cd "$(Build.SourcesDirectory)" + tar --exclude='./.git' \ + --exclude='./vhdbuilder/cisassessor*' \ + --exclude='./kata-containers*.img' \ + --exclude='./kata-containers-igvm*.img' \ + --exclude='./kata-containers-cc.img' \ + --exclude='./kata-containers-initrd-base.img' \ + --exclude='./igvm-measurement' \ + -czf "$(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz" . + ls -lh "$(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz" + condition: and(succeeded(), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) + displayName: Bundle source for arm64 COSI convert + + - task: PublishPipelineArtifact@1 + condition: and(succeeded(), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) + displayName: Publish source bundle for arm64 COSI convert + inputs: + artifactName: agentbaker-sources-$(System.JobName) + targetPath: $(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz + - template: ./.template-override-components-json.yaml parameters: overrideBranch: ${{ parameters.overrideBranch }} From 5483cebf9fd998b94665890917424693f824954c Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Tue, 21 Jul 2026 17:36:33 -0400 Subject: [PATCH 29/60] arm64 COSI: bundle source AFTER checkout, use artifactName suffix Two issues in build 173200118: 1. Bundle+Publish steps ran BEFORE the pre-existing '- checkout: self' step inside .template-override-components-json.yaml, so tar ran against an empty Build.SourcesDirectory (159-byte tarball) and the downstream Build VHD packer step also hit an empty tree ("make: packer.mk: No such file or directory"). Fix: move Bundle+Publish AFTER the .template-override-components-json template inclusion so checkout: self runs first. 2. Artifact name used $(System.JobName) which resolves to '__default' for non-matrix jobs, so both arm64 ACL build jobs collided on 'agentbaker-sources-__default'. Fix: use ${{ parameters.artifactName }} (per-job compile-time value like 'acl-arm64-tl-gen2') on both publish and download sides. Drop the now-unused buildJobName parameter. --- .pipelines/.vsts-vhd-builder-release.yaml | 2 -- .../.builder-cosi-convert-template.yaml | 4 +-- .../templates/.builder-release-template.yaml | 27 +++++++++---------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index bd8a511934c..49d78686018 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -953,7 +953,6 @@ stages: - template: ./templates/.builder-cosi-convert-template.yaml parameters: artifactName: acl-arm64-tl-gen2 - buildJobName: buildaclarm64tlgen2 - job: convertaclarm64fipstlgen2 displayName: Convert ACL ARM64 FIPS TL Gen2 VHD to COSI (native arm64) condition: and(succeeded(), eq('${{ parameters.buildaclarm64fipstlgen2 }}', true)) @@ -984,7 +983,6 @@ stages: - template: ./templates/.builder-cosi-convert-template.yaml parameters: artifactName: acl-arm64-fips-tl-gen2 - buildJobName: buildaclarm64fipstlgen2 - job: build2404arm64gbgen2containerd condition: eq('${{ parameters.build2404arm64gbgen2containerd }}', true) dependsOn: [ ] diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index 15f2f0673a2..f68e22f40d0 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -17,8 +17,6 @@ parameters: - name: artifactName type: string - - name: buildJobName - type: string steps: - checkout: none @@ -120,7 +118,7 @@ steps: - task: DownloadPipelineArtifact@2 displayName: Download AgentBaker source bundle inputs: - artifact: agentbaker-sources-${{ parameters.buildJobName }} + artifact: agentbaker-sources-${{ parameters.artifactName }} path: $(Pipeline.Workspace)/agentbaker-sources - bash: | diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index c8a17845e65..f43b3163c69 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -10,20 +10,20 @@ parameters: default: master steps: + - template: ./.template-override-components-json.yaml + parameters: + overrideBranch: ${{ parameters.overrideBranch }} + useOverrides: ${{ parameters.useOverrides }} + # For arm64 ACL: publish a source bundle for the separate native-arm64 COSI - # convert job. The arm64 pool has no git so `checkout: self` fails there; the - # convert job downloads this bundle instead of cloning. Runs first (before any - # downloads) so the bundle stays small. + # convert job (the arm64 pool has no git, so its `checkout: self` is disabled + # and it downloads this bundle instead). Must run AFTER the checkout above but + # BEFORE Download CIS scanner (which drops binaries into vhdbuilder/ that + # would otherwise bloat the tarball). - bash: | set -euo pipefail cd "$(Build.SourcesDirectory)" tar --exclude='./.git' \ - --exclude='./vhdbuilder/cisassessor*' \ - --exclude='./kata-containers*.img' \ - --exclude='./kata-containers-igvm*.img' \ - --exclude='./kata-containers-cc.img' \ - --exclude='./kata-containers-initrd-base.img' \ - --exclude='./igvm-measurement' \ -czf "$(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz" . ls -lh "$(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz" condition: and(succeeded(), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) @@ -33,14 +33,11 @@ steps: condition: and(succeeded(), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) displayName: Publish source bundle for arm64 COSI convert inputs: - artifactName: agentbaker-sources-$(System.JobName) + # artifactName is the per-job param (e.g. "acl-arm64-tl-gen2"); using it + # here guarantees a unique artifact name per build job at compile time. + artifactName: agentbaker-sources-${{ parameters.artifactName }} targetPath: $(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz - - template: ./.template-override-components-json.yaml - parameters: - overrideBranch: ${{ parameters.overrideBranch }} - useOverrides: ${{ parameters.useOverrides }} - - task: UniversalPackages@0 displayName: Download CIS scanner inputs: From 0185d6b20b956c1a304701f2d036cb5d26864c37 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Tue, 21 Jul 2026 20:25:20 -0400 Subject: [PATCH 30/60] arm64 COSI: install CGO/OpenSSL deps for Microsoft Go build Microsoft's Go distribution (installed by hack/setup_golang.sh via packages.microsoft.com) defaults to GOEXPERIMENT=systemcrypto, which requires CGO_ENABLED=1 plus a C compiler and OpenSSL dev headers. Without them, 'go build ./cmd/cosi-upload' fails with: Using GOEXPERIMENT=systemcrypto on Linux requires CGO_ENABLED=1 undefined: systemFIPSMode / backendEnabled Add build-essential + libssl-dev + pkg-config (apt) / gcc + make + openssl-devel + pkg-config (tdnf/dnf) to the bootstrap step. --- .pipelines/templates/.builder-cosi-convert-template.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index f68e22f40d0..3977fd89a8a 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -60,6 +60,15 @@ steps: # Baseline utilities used by scripts and downloads below. pkg_install ca-certificates curl tar gnupg make jq + # Microsoft Go (installed later by hack/setup_golang.sh) defaults to + # GOEXPERIMENT=systemcrypto, which needs CGO + a C compiler + OpenSSL dev + # headers to build. Without them, `go build ./cmd/cosi-upload` fails with + # "undefined: systemFIPSMode" in crypto/internal/backend/fips140. + case "$PKG" in + apt) pkg_install build-essential libssl-dev pkg-config ;; + tdnf|dnf) pkg_install gcc make openssl-devel pkg-config ;; + esac + # git isn't used by the convert scripts, but install it so any implicit # agent-side cleanup checkouts don't tank the job. if ! command -v git >/dev/null 2>&1; then From c0515a7b584ea2150bb733717c759a8988e7acbf Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Tue, 21 Jul 2026 23:33:47 -0400 Subject: [PATCH 31/60] arm64 COSI: install glibc-devel on Azure Linux for CGO builds Build 173232883 still failed 'go build ./cmd/cosi-upload' with: fatal error: stdint.h / errno.h / pthread.h / grp.h: No such file make: *** [packer.mk:113: build-cosi-upload] Error 1 Root cause: the arm64 pool is Azure Linux 3.0 (tdnf), not Ubuntu. On apt, build-essential pulls libc6-dev automatically; tdnf/dnf's gcc has no such dependency, so libc headers under /usr/include are missing. Add glibc-devel to the tdnf/dnf install list. --- .pipelines/templates/.builder-cosi-convert-template.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index 3977fd89a8a..23be68ee1cf 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -60,13 +60,11 @@ steps: # Baseline utilities used by scripts and downloads below. pkg_install ca-certificates curl tar gnupg make jq - # Microsoft Go (installed later by hack/setup_golang.sh) defaults to - # GOEXPERIMENT=systemcrypto, which needs CGO + a C compiler + OpenSSL dev - # headers to build. Without them, `go build ./cmd/cosi-upload` fails with - # "undefined: systemFIPSMode" in crypto/internal/backend/fips140. + # CGO deps for `go build ./cmd/cosi-upload` (MS Go / azl3 golang enables + # systemcrypto, which needs a C compiler + libc + OpenSSL headers). case "$PKG" in apt) pkg_install build-essential libssl-dev pkg-config ;; - tdnf|dnf) pkg_install gcc make openssl-devel pkg-config ;; + tdnf|dnf) pkg_install gcc make glibc-devel openssl-devel pkg-config ;; esac # git isn't used by the convert scripts, but install it so any implicit From 2b44add030f99e0bcb659dca1f475ff15d8b9f30 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Wed, 22 Jul 2026 08:19:31 -0400 Subject: [PATCH 32/60] arm64 COSI: build cosi-upload cgo-free (ms_nocgo_opensslcrypto) The 'Upload COSI to PMC' step failed on both the native arm64 convert pool and the x64 ACL build job: - arm64 (Azure Linux): 'go build ./cmd/cosi-upload' ran a CGO build that needs a full C toolchain. The minimal pool lacks 'as' (binutils) and linux/errno.h (kernel-headers), so the build failed. - x64 (Ubuntu): build-cosi-upload's 'setup-golang' prereq re-ran setup_golang.sh (apt purge+reinstall of MS Go) a second time and raced another apt process on /var/lib/apt/lists/lock -> Error 100. Build cosi-upload the same way as every other Go binary in packer.mk (build-aks-node-controller, build-image-fetcher, build-lister-binary): GOEXPERIMENT=ms_nocgo_opensslcrypto CGO_ENABLED=0. This keeps FIPS/OpenSSL crypto compliance (dlopen backend) with no build-time C toolchain, and drops the redundant setup-golang prereq. Also removes the now-unneeded CGO/OpenSSL-dev bootstrap installs from the arm64 convert template. --- .../templates/.builder-cosi-convert-template.yaml | 10 ++++------ packer.mk | 5 +++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index 23be68ee1cf..d4fdddb1cda 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -60,12 +60,10 @@ steps: # Baseline utilities used by scripts and downloads below. pkg_install ca-certificates curl tar gnupg make jq - # CGO deps for `go build ./cmd/cosi-upload` (MS Go / azl3 golang enables - # systemcrypto, which needs a C compiler + libc + OpenSSL headers). - case "$PKG" in - apt) pkg_install build-essential libssl-dev pkg-config ;; - tdnf|dnf) pkg_install gcc make glibc-devel openssl-devel pkg-config ;; - esac + # cosi-upload is built with `GOEXPERIMENT=ms_nocgo_opensslcrypto CGO_ENABLED=0` + # (see packer.mk build-cosi-upload), matching every other Go binary in this + # repo. That keeps FIPS/OpenSSL crypto compliance while avoiding a build-time + # C toolchain, so no gcc/binutils/libc/OpenSSL-dev packages are needed here. # git isn't used by the convert scripts, but install it so any implicit # agent-side cleanup checkouts don't tank the job. diff --git a/packer.mk b/packer.mk index 2f7ab4aef68..39d3c2d66d6 100755 --- a/packer.mk +++ b/packer.mk @@ -109,8 +109,9 @@ convert-sig-to-classic-storage-account-blob: az-login convert-vhd-to-cosi: az-login @./vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh "$${IMG_CUSTOMIZER_ALLOW_FALLBACK:-false}" -build-cosi-upload: setup-golang - @go build -o bin/cosi-upload ./cmd/cosi-upload +build-cosi-upload: + @echo "Building cosi-upload binary" + @GOEXPERIMENT=ms_nocgo_opensslcrypto CGO_ENABLED=0 go build -o bin/cosi-upload ./cmd/cosi-upload upload-cosi-to-pmc: build-cosi-upload @./vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh From 51ae92eed179d854c8da3983d300d2b4cf5c2c93 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:34:35 -0400 Subject: [PATCH 33/60] chore(pipeline): clarify ARM64 COSI job names --- .pipelines/.vsts-vhd-builder-release.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index 3f1433311d6..c82f4059237 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -839,6 +839,7 @@ stages: overrideBranch: ${{ parameters.overrideBranch }} artifactName: acl-tl-gen2 - job: buildaclarm64tlgen2 + displayName: 'ACL ARM64 TL Gen2 | Build' condition: eq('${{ parameters.buildaclarm64tlgen2 }}', true) dependsOn: [ ] timeoutInMinutes: 360 @@ -893,6 +894,7 @@ stages: overrideBranch: ${{ parameters.overrideBranch }} artifactName: acl-fips-tl-gen2 - job: buildaclarm64fipstlgen2 + displayName: 'ACL ARM64 FIPS TL Gen2 | Build' condition: eq('${{ parameters.buildaclarm64fipstlgen2 }}', true) dependsOn: [ ] timeoutInMinutes: 360 @@ -924,7 +926,7 @@ stages: # don't work under qemu emulation on the x86_64 build pool). These jobs run on # the native arm64 pool to convert the staged VHD and publish the COSI. - job: convertaclarm64tlgen2 - displayName: Convert ACL ARM64 TL Gen2 VHD to COSI (native arm64) + displayName: 'ACL ARM64 TL Gen2 | COSI' condition: and(succeeded(), eq('${{ parameters.buildaclarm64tlgen2 }}', true)) dependsOn: buildaclarm64tlgen2 timeoutInMinutes: 120 @@ -954,7 +956,7 @@ stages: parameters: artifactName: acl-arm64-tl-gen2 - job: convertaclarm64fipstlgen2 - displayName: Convert ACL ARM64 FIPS TL Gen2 VHD to COSI (native arm64) + displayName: 'ACL ARM64 FIPS TL Gen2 | COSI' condition: and(succeeded(), eq('${{ parameters.buildaclarm64fipstlgen2 }}', true)) dependsOn: buildaclarm64fipstlgen2 timeoutInMinutes: 120 From 6dc359aea1e0c74097619c0e6655dea5b491063f Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Thu, 30 Jul 2026 15:00:39 -0400 Subject: [PATCH 34/60] fix(pipeline): align ARM64 COSI job names --- .pipelines/.vsts-vhd-builder-release.yaml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index c49ef3b2060..e66518aa199 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -839,7 +839,6 @@ stages: overrideBranch: ${{ parameters.overrideBranch }} artifactName: acl-tl-gen2 - job: buildaclarm64tlgen2 - displayName: 'ACL ARM64 TL Gen2 | Build' condition: eq('${{ parameters.buildaclarm64tlgen2 }}', true) dependsOn: [ ] timeoutInMinutes: 360 @@ -894,7 +893,6 @@ stages: overrideBranch: ${{ parameters.overrideBranch }} artifactName: acl-fips-tl-gen2 - job: buildaclarm64fipstlgen2 - displayName: 'ACL ARM64 FIPS TL Gen2 | Build' condition: eq('${{ parameters.buildaclarm64fipstlgen2 }}', true) dependsOn: [ ] timeoutInMinutes: 360 @@ -925,8 +923,7 @@ stages: # VHD in blob storage and skip COSI (ImageCustomizer's btrfs subvolume ioctls # don't work under qemu emulation on the x86_64 build pool). These jobs run on # the native arm64 pool to convert the staged VHD and publish the COSI. - - job: convertaclarm64tlgen2 - displayName: 'ACL ARM64 TL Gen2 | COSI' + - job: buildaclarm64tlgen2cosi condition: and(succeeded(), eq('${{ parameters.buildaclarm64tlgen2 }}', true)) dependsOn: buildaclarm64tlgen2 timeoutInMinutes: 120 @@ -955,8 +952,7 @@ stages: - template: ./templates/.builder-cosi-convert-template.yaml parameters: artifactName: acl-arm64-tl-gen2 - - job: convertaclarm64fipstlgen2 - displayName: 'ACL ARM64 FIPS TL Gen2 | COSI' + - job: buildaclarm64fipstlgen2cosi condition: and(succeeded(), eq('${{ parameters.buildaclarm64fipstlgen2 }}', true)) dependsOn: buildaclarm64fipstlgen2 timeoutInMinutes: 120 From b18998218e1fa908f6e674191838a3bd134ab5c7 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Thu, 30 Jul 2026 16:26:42 -0400 Subject: [PATCH 35/60] fix(pipeline): distinguish ARM64 COSI conversion jobs --- .pipelines/.vsts-vhd-builder-release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index e66518aa199..54a8717d3f6 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -923,7 +923,7 @@ stages: # VHD in blob storage and skip COSI (ImageCustomizer's btrfs subvolume ioctls # don't work under qemu emulation on the x86_64 build pool). These jobs run on # the native arm64 pool to convert the staged VHD and publish the COSI. - - job: buildaclarm64tlgen2cosi + - job: convertaclarm64tlgen2 condition: and(succeeded(), eq('${{ parameters.buildaclarm64tlgen2 }}', true)) dependsOn: buildaclarm64tlgen2 timeoutInMinutes: 120 @@ -952,7 +952,7 @@ stages: - template: ./templates/.builder-cosi-convert-template.yaml parameters: artifactName: acl-arm64-tl-gen2 - - job: buildaclarm64fipstlgen2cosi + - job: convertaclarm64fipstlgen2 condition: and(succeeded(), eq('${{ parameters.buildaclarm64fipstlgen2 }}', true)) dependsOn: buildaclarm64fipstlgen2 timeoutInMinutes: 120 From c8cf1ed9172fe47f88d494296ac305f8e07d3112 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Thu, 6 Aug 2026 14:34:25 -0400 Subject: [PATCH 36/60] Normalize image version and fips/non-fips --- .../convert_vhd_to_cosi_spec.sh | 85 +++++++++++++++++++ .../scripts/convert-vhd-to-cosi.sh | 49 ++++++++++- 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh diff --git a/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh b/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh new file mode 100644 index 00000000000..249e82f8337 --- /dev/null +++ b/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +# Tests for generate_cosi_package_version in +# vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh. +# +# Nebraska validates COSI package versions as strict SemVer, which rejects +# leading zeros in numeric components (e.g. 202608.06.0). These tests pin the +# normalization: leading zeros stripped, -fips appended only for FIPS builds, +# and never duplicated. + +Describe 'generate_cosi_package_version' + setup() { + # Source only the functions (guarded by ${__SOURCED__:+return}), not the + # main conversion flow. + # shellcheck disable=SC1090 + __SOURCED__=1 . "./vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh" + } + BeforeEach 'setup' + + Describe 'strips per-component leading zeros' + It 'normalizes day 01 (non-FIPS)' + When call generate_cosi_package_version "202608.01.0" "false" + The status should be success + The output should equal "202608.1.0" + End + + It 'normalizes day 06 (non-FIPS)' + When call generate_cosi_package_version "202608.06.0" "false" + The status should be success + The output should equal "202608.6.0" + End + + It 'leaves day 10 unchanged (non-FIPS)' + When call generate_cosi_package_version "202608.10.0" "false" + The status should be success + The output should equal "202608.10.0" + End + End + + Describe 'appends -fips only for FIPS builds' + It 'emits -fips when ENABLE_FIPS is true' + When call generate_cosi_package_version "202608.06.0" "true" + The status should be success + The output should equal "202608.6.0-fips" + End + + It 'omits -fips when ENABLE_FIPS is false' + When call generate_cosi_package_version "202608.06.0" "false" + The status should be success + The output should equal "202608.6.0" + End + + It 'treats ENABLE_FIPS case-insensitively (True)' + When call generate_cosi_package_version "202608.10.2" "True" + The status should be success + The output should equal "202608.10.2-fips" + End + + It 'does not duplicate -fips when the version already contains it' + When call generate_cosi_package_version "202608.06.0-fips" "true" + The status should be success + The output should equal "202608.6.0-fips" + End + End + + Describe 'rejects a core that is not exactly three numeric components' + It 'fails with too few components' + When call generate_cosi_package_version "202608.06" "false" + The status should be failure + The error should be present + End + + It 'fails with too many components' + When call generate_cosi_package_version "202608.06.0.1" "false" + The status should be failure + The error should be present + End + + It 'fails with a non-numeric component' + When call generate_cosi_package_version "202608.aug.0" "false" + The status should be failure + The error should be present + End + End +End diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index a0e9c49b2ec..550f5d48666 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -4,6 +4,46 @@ set -euo pipefail # Converts an ACL VHD to COSI and stages it for the separate "Upload COSI to PMC" # task (which runs under PMC's service connection). +# Nebraska requires strict SemVer, so strip per-component leading zeros (e.g. +# 202608.06.0 -> 202608.6.0) and append -fips for FIPS builds. +generate_cosi_package_version() { + local raw_version="$1" + local enable_fips="$2" + + # Drop any existing -fips so it is never duplicated; ENABLE_FIPS re-adds it. + local core="$raw_version" + case "${raw_version,,}" in + *-fips) core="${raw_version%-*}" ;; + esac + + # Require exactly three numeric components (major.minor.patch). + local major minor patch extra + IFS='.' read -r major minor patch extra <<<"$core" + if [ -n "$extra" ]; then + echo "generate_cosi_package_version: '${core}' has more than three components" >&2 + return 1 + fi + local component + for component in "$major" "$minor" "$patch"; do + case "$component" in + ''|*[!0-9]*) + echo "generate_cosi_package_version: '${core}' must have exactly three numeric components" >&2 + return 1 + ;; + esac + done + + # 10# forces base-10 so values like 06 are not parsed as octal. + local version="$((10#$major)).$((10#$minor)).$((10#$patch))" + if [ "${enable_fips,,}" = "true" ]; then + version="${version}-fips" + fi + printf '%s\n' "$version" +} + +# Source-guard: functions above are unit-tested; the flow below skips when sourced. +${__SOURCED__:+return} + required_env_vars=( "DESTINATION_STORAGE_CONTAINER" "CAPTURED_SIG_VERSION" @@ -142,6 +182,13 @@ if [ -z "${IMAGE_VERSION:-}" ]; then echo "IMAGE_VERSION was not set, defaulting to ${IMAGE_VERSION}" fi +# Normalize only the Nebraska-facing version; IMAGE_VERSION itself is unchanged. +if ! COSI_IMAGE_VERSION="$(generate_cosi_package_version "${IMAGE_VERSION}" "${ENABLE_FIPS:-false}")"; then + echo "##vso[task.logissue type=error]Failed to derive a strict-SemVer COSI package version from IMAGE_VERSION='${IMAGE_VERSION}' (ENABLE_FIPS='${ENABLE_FIPS:-false}')" + exit 1 +fi +echo "Normalized COSI package version: ${COSI_IMAGE_VERSION} (from IMAGE_VERSION=${IMAGE_VERSION}, ENABLE_FIPS=${ENABLE_FIPS:-false})" + # ARCH_LOWER is derived once near the top of the script and reused here. if [ "${ARCH_LOWER,,}" = "arm64" ]; then IMAGE_ARCH="Arm64" @@ -163,7 +210,7 @@ cat < cosi-publishing-info.json "offer_name": "${OFFER_NAME:-}", "hyperv_generation": "${HYPERV_GENERATION:-}", "image_architecture": "${IMAGE_ARCH}", - "image_version": "${IMAGE_VERSION}" + "image_version": "${COSI_IMAGE_VERSION}" } EOF From e75d365a8f3624db3eebf5c76b38e198898e2b43 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Tue, 8 Sep 2026 09:51:40 -0400 Subject: [PATCH 37/60] Fix --- .pipelines/templates/.builder-cosi-convert-template.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index d4fdddb1cda..bccba85ec99 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -86,7 +86,12 @@ steps: # Azure CLI. if ! command -v az >/dev/null 2>&1; then case "$PKG" in - apt) curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash ;; + apt) + tmp="$(mktemp -d)" + curl -fsSL https://aka.ms/InstallAzureCLIDeb -o "$tmp/install-azure-cli.sh" + sudo bash "$tmp/install-azure-cli.sh" + rm -rf "$tmp" + ;; tdnf|dnf) pkg_install azure-cli ;; esac fi From 3978b410b9f9279cfa92ef8da26dd4b2b6ee2d64 Mon Sep 17 00:00:00 2001 From: "aks-node-assistant[bot]" <190555641+aks-node-assistant[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:01:35 +0000 Subject: [PATCH 38/60] chore: tidy go.mod --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index d2230a5cec9..a530b8e9433 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,6 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/spf13/pflag v1.0.9 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/crypto v0.51.0 // indirect From eba427e385452ffc4ce3d703826015097845b495 Mon Sep 17 00:00:00 2001 From: "aks-node-assistant[bot]" <190555641+aks-node-assistant[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:01:37 +0000 Subject: [PATCH 39/60] chore: tidy go.sum --- go.sum | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.sum b/go.sum index 2c62db75348..1ff6b91a2f9 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,6 @@ github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687/go.mod h1:Salmysdw github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -102,8 +100,6 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjL github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= From d06bb2c0421e5229d8e0083ae72754c196a88aef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:17:30 +0000 Subject: [PATCH 40/60] Harden COSI conversion bootstrap Co-authored-by: adamsanaglo <56308410+adamsanaglo@users.noreply.github.com> --- .pipelines/templates/.builder-cosi-convert-template.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index bccba85ec99..9d2ca70deeb 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -80,7 +80,7 @@ steps: fi sudo systemctl start docker 2>/dev/null || sudo service docker start 2>/dev/null || true # Give the agent user access to the docker socket for the duration of the job. - sudo chmod 666 /var/run/docker.sock 2>/dev/null || true + sudo chown "$(id -u):$(id -g)" /var/run/docker.sock 2>/dev/null || true docker version # Azure CLI. @@ -104,10 +104,14 @@ steps: *) AZCOPY_URL=https://aka.ms/downloadazcopy-v10-linux ;; esac tmp="$(mktemp -d)" - curl -sSL "$AZCOPY_URL" -o "$tmp/azcopy.tgz" + curl -fsSL "$AZCOPY_URL" -o "$tmp/azcopy.tgz" tar -xzf "$tmp/azcopy.tgz" -C "$tmp" # extracted dir is like azcopy_linux_arm64_10.x.x/ azcopy_bin="$(find "$tmp" -maxdepth 2 -type f -name azcopy | head -n1)" + if [ -z "$azcopy_bin" ] || [ ! -x "$azcopy_bin" ]; then + echo "##vso[task.logissue type=error]azcopy archive did not contain an executable binary" + exit 1 + fi sudo install -m 0755 "$azcopy_bin" /usr/local/bin/azcopy rm -rf "$tmp" fi From 755fb100103b1870f5fa50b08aa9200919c1c1ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:19:57 +0000 Subject: [PATCH 41/60] Finalize review feedback fixes Co-authored-by: adamsanaglo <56308410+adamsanaglo@users.noreply.github.com> --- .../parser/testdata/AKSUbuntu2204+China/generatedCSECommand | 2 +- .../AKSUbuntu2204+CloudProviderOverrides/generatedCSECommand | 1 + .../testdata/AKSUbuntu2204+Containerd+MIG/generatedCSECommand | 2 +- .../testdata/AKSUbuntu2204+CustomCloud/generatedCSECommand | 2 +- .../testdata/AKSUbuntu2204+CustomOSConfig/generatedCSECommand | 2 +- .../AKSUbuntu2204+DefaultPubkeyAuth/generatedCSECommand | 1 + .../AKSUbuntu2204+DisablePubkeyAuth/generatedCSECommand | 1 + .../testdata/AKSUbuntu2204+EnablePubkeyAuth/generatedCSECommand | 1 + .../AKSUbuntu2204+LocalDNS+HostsPlugin/generatedCSECommand | 2 +- .../parser/testdata/AKSUbuntu2204+LocalDNS/generatedCSECommand | 2 +- .../testdata/AKSUbuntu2204+SSHStatusOn/generatedCSECommand | 2 +- .../generatedCSECommand | 2 +- .../testdata/Compatibility+EmptyConfig/generatedCSECommand | 2 +- 13 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 aks-node-controller/parser/testdata/AKSUbuntu2204+CloudProviderOverrides/generatedCSECommand create mode 100644 aks-node-controller/parser/testdata/AKSUbuntu2204+DefaultPubkeyAuth/generatedCSECommand create mode 100644 aks-node-controller/parser/testdata/AKSUbuntu2204+DisablePubkeyAuth/generatedCSECommand create mode 100644 aks-node-controller/parser/testdata/AKSUbuntu2204+EnablePubkeyAuth/generatedCSECommand diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+China/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+China/generatedCSECommand index 03ebd588cae..85480046d68 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+China/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+China/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; cloud-init status --wait > /dev/null 2>&1; [ $? -ne 0 ] && echo 'cloud-init failed' >> ${PROVISION_OUTPUT} && exit 1; echo "cloud-init succeeded" >> ${PROVISION_OUTPUT}; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+CloudProviderOverrides/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+CloudProviderOverrides/generatedCSECommand new file mode 100644 index 00000000000..85480046d68 --- /dev/null +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+CloudProviderOverrides/generatedCSECommand @@ -0,0 +1 @@ +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+Containerd+MIG/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+Containerd+MIG/generatedCSECommand index 5b7f13031f9..85480046d68 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+Containerd+MIG/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+Containerd+MIG/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; LOCATION="southcentralus" /opt/azure/containers/init-aks-cloud.sh >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomCloud/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomCloud/generatedCSECommand index a4df89c1447..85480046d68 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomCloud/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomCloud/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; cloud-init status --wait > /dev/null 2>&1; [ $? -ne 0 ] && echo 'cloud-init failed' >> ${PROVISION_OUTPUT} && exit 1; echo "cloud-init succeeded" >> ${PROVISION_OUTPUT}; REPO_DEPOT_ENDPOINT="" /opt/azure/containers/init-aks-cloud.sh >> /var/log/azure/cluster-provision.log 2>&1; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomOSConfig/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomOSConfig/generatedCSECommand index 03ebd588cae..85480046d68 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomOSConfig/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomOSConfig/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; cloud-init status --wait > /dev/null 2>&1; [ $? -ne 0 ] && echo 'cloud-init failed' >> ${PROVISION_OUTPUT} && exit 1; echo "cloud-init succeeded" >> ${PROVISION_OUTPUT}; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+DefaultPubkeyAuth/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+DefaultPubkeyAuth/generatedCSECommand new file mode 100644 index 00000000000..85480046d68 --- /dev/null +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+DefaultPubkeyAuth/generatedCSECommand @@ -0,0 +1 @@ +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+DisablePubkeyAuth/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+DisablePubkeyAuth/generatedCSECommand new file mode 100644 index 00000000000..85480046d68 --- /dev/null +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+DisablePubkeyAuth/generatedCSECommand @@ -0,0 +1 @@ +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+EnablePubkeyAuth/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+EnablePubkeyAuth/generatedCSECommand new file mode 100644 index 00000000000..85480046d68 --- /dev/null +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+EnablePubkeyAuth/generatedCSECommand @@ -0,0 +1 @@ +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS+HostsPlugin/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS+HostsPlugin/generatedCSECommand index f73190d544c..85480046d68 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS+HostsPlugin/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS+HostsPlugin/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS/generatedCSECommand index f73190d544c..85480046d68 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+SSHStatusOn/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+SSHStatusOn/generatedCSECommand index 03ebd588cae..85480046d68 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+SSHStatusOn/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+SSHStatusOn/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; cloud-init status --wait > /dev/null 2>&1; [ $? -ne 0 ] && echo 'cloud-init failed' >> ${PROVISION_OUTPUT} && exit 1; echo "cloud-init succeeded" >> ${PROVISION_OUTPUT}; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AzureLinuxv2+Kata+DisableUnattendedUpgrades=false/generatedCSECommand b/aks-node-controller/parser/testdata/AzureLinuxv2+Kata+DisableUnattendedUpgrades=false/generatedCSECommand index 03ebd588cae..85480046d68 100644 --- a/aks-node-controller/parser/testdata/AzureLinuxv2+Kata+DisableUnattendedUpgrades=false/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AzureLinuxv2+Kata+DisableUnattendedUpgrades=false/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; cloud-init status --wait > /dev/null 2>&1; [ $? -ne 0 ] && echo 'cloud-init failed' >> ${PROVISION_OUTPUT} && exit 1; echo "cloud-init succeeded" >> ${PROVISION_OUTPUT}; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/Compatibility+EmptyConfig/generatedCSECommand b/aks-node-controller/parser/testdata/Compatibility+EmptyConfig/generatedCSECommand index fd55aaa5299..85480046d68 100644 --- a/aks-node-controller/parser/testdata/Compatibility+EmptyConfig/generatedCSECommand +++ b/aks-node-controller/parser/testdata/Compatibility+EmptyConfig/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; LOCATION="" /opt/azure/containers/init-aks-cloud.sh >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file From aa30655cb6183a4972f1159d4f9bc8403d357db8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:42:55 +0000 Subject: [PATCH 42/60] Harden COSI bootstrap downloads Co-authored-by: adamsanaglo <56308410+adamsanaglo@users.noreply.github.com> --- .../templates/.builder-cosi-convert-template.yaml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index 9d2ca70deeb..cdc1d25cedd 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -100,11 +100,18 @@ steps: # azcopy static binary. if ! command -v azcopy >/dev/null 2>&1; then case "$(uname -m)" in - aarch64|arm64) AZCOPY_URL=https://aka.ms/downloadazcopy-v10-linux-arm64 ;; - *) AZCOPY_URL=https://aka.ms/downloadazcopy-v10-linux ;; + aarch64|arm64) + AZCOPY_URL=https://azcopyvnext.azureedge.net/releases/release-10.22.1-20231220/azcopy_linux_arm64_10.22.1.tar.gz + AZCOPY_SHA256=4db9a4b48abc7775f1a5d6d928afc42361dcc57bbfcde23ac82e4c419a0dc8fc + ;; + *) + AZCOPY_URL=https://azcopyvnext.azureedge.net/releases/release-10.22.1-20231220/azcopy_linux_amd64_10.22.1.tar.gz + AZCOPY_SHA256=7549424d56ab2d8b4033c84c2a9bb167dc2dcbb23998acd7fffb37bc1a71a267 + ;; esac tmp="$(mktemp -d)" curl -fsSL "$AZCOPY_URL" -o "$tmp/azcopy.tgz" + echo "$AZCOPY_SHA256 $tmp/azcopy.tgz" | sha256sum --check --status tar -xzf "$tmp/azcopy.tgz" -C "$tmp" # extracted dir is like azcopy_linux_arm64_10.x.x/ azcopy_bin="$(find "$tmp" -maxdepth 2 -type f -name azcopy | head -n1)" @@ -117,10 +124,10 @@ steps: fi azcopy --version - # Go: on Ubuntu, hack/setup_golang.sh will install Microsoft Go via apt. - # On tdnf/dnf hosts it expects Go to already be on PATH, so install it here. + # Go is needed to build cosi-upload. if ! command -v go >/dev/null 2>&1; then case "$PKG" in + apt) pkg_install golang-go ;; tdnf|dnf) pkg_install golang ;; esac fi From eb5d9d18f584b16981f96ec20e5b55443aae9d1d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:53:25 +0000 Subject: [PATCH 43/60] Create COSI uploader output directory Co-authored-by: adamsanaglo <56308410+adamsanaglo@users.noreply.github.com> --- packer.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/packer.mk b/packer.mk index 49a4d6ba3c6..20a9bf556a5 100755 --- a/packer.mk +++ b/packer.mk @@ -109,6 +109,7 @@ convert-vhd-to-cosi: az-login build-cosi-upload: @echo "Building cosi-upload binary" + @mkdir -p bin @GOEXPERIMENT=ms_nocgo_opensslcrypto CGO_ENABLED=0 go build -o bin/cosi-upload ./cmd/cosi-upload upload-cosi-to-pmc: build-cosi-upload From e6eacdc4db3741326a8a74af245f48abe72c834a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:26:20 +0000 Subject: [PATCH 44/60] Install Azure CLI on apt via pinned Microsoft repository Co-authored-by: adamsanaglo <56308410+adamsanaglo@users.noreply.github.com> --- .../templates/.builder-cosi-convert-template.yaml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index cdc1d25cedd..3a1cb9c90c0 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -87,10 +87,21 @@ steps: if ! command -v az >/dev/null 2>&1; then case "$PKG" in apt) + # Install azure-cli from the pinned Microsoft apt repository so the + # install path is auditable and versionable (avoids piping a remote + # script into `sudo bash`). + pkg_install ca-certificates curl gnupg apt-transport-https lsb-release + sudo install -d -m 0755 /etc/apt/keyrings tmp="$(mktemp -d)" - curl -fsSL https://aka.ms/InstallAzureCLIDeb -o "$tmp/install-azure-cli.sh" - sudo bash "$tmp/install-azure-cli.sh" + curl -fsSL https://packages.microsoft.com/keys/microsoft.asc -o "$tmp/microsoft.asc" + gpg --dearmor < "$tmp/microsoft.asc" > "$tmp/microsoft.gpg" + sudo install -m 0644 "$tmp/microsoft.gpg" /etc/apt/keyrings/microsoft.gpg rm -rf "$tmp" + AZ_DIST="$(lsb_release -cs)" + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/azure-cli/ ${AZ_DIST} main" \ + | sudo tee /etc/apt/sources.list.d/azure-cli.list >/dev/null + sudo apt-get update + pkg_install azure-cli ;; tdnf|dnf) pkg_install azure-cli ;; esac From d2a6e47aadf003d5d722457f87c63a8c3bdb911c Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:28:15 -0400 Subject: [PATCH 45/60] Improve Go installation logic in builder template Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../.builder-cosi-convert-template.yaml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index 3a1cb9c90c0..ffed55883c7 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -136,13 +136,15 @@ steps: azcopy --version # Go is needed to build cosi-upload. + # On apt-based agents, install the repo-pinned Go toolchain after sources are extracted. if ! command -v go >/dev/null 2>&1; then - case "$PKG" in - apt) pkg_install golang-go ;; - tdnf|dnf) pkg_install golang ;; - esac + if [ "$PKG" = apt ]; then + echo "Go not found yet; will install pinned version after source extraction" + else + pkg_install golang + fi fi - go version || echo "Go will be installed by hack/setup_golang.sh" + command -v go >/dev/null 2>&1 && go version || true echo "==== Tooling bootstrap complete ====" displayName: Bootstrap arm64 convert tooling @@ -158,7 +160,11 @@ steps: mkdir -p "$(Build.SourcesDirectory)" tar -xzf "$(Pipeline.Workspace)/agentbaker-sources/agentbaker-sources.tar.gz" -C "$(Build.SourcesDirectory)" ls "$(Build.SourcesDirectory)" - displayName: Extract AgentBaker source bundle + cd "$(Build.SourcesDirectory)" + if command -v apt-get >/dev/null 2>&1; then + ./hack/setup_golang.sh + fi + go version - bash: | SKU_NAME=${OS_VERSION} && \ From 385cc9997a95233223671c30af271ebd86ef157b Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:09:11 -0400 Subject: [PATCH 46/60] Revert unrelated CSE snapshot regeneration --- .../parser/testdata/AKSUbuntu2204+China/generatedCSECommand | 2 +- .../testdata/AKSUbuntu2204+Containerd+MIG/generatedCSECommand | 2 +- .../testdata/AKSUbuntu2204+CustomCloud/generatedCSECommand | 2 +- .../testdata/AKSUbuntu2204+CustomOSConfig/generatedCSECommand | 2 +- .../AKSUbuntu2204+LocalDNS+HostsPlugin/generatedCSECommand | 2 +- .../parser/testdata/AKSUbuntu2204+LocalDNS/generatedCSECommand | 2 +- .../testdata/AKSUbuntu2204+SSHStatusOn/generatedCSECommand | 2 +- .../generatedCSECommand | 2 +- .../testdata/Compatibility+EmptyConfig/generatedCSECommand | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+China/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+China/generatedCSECommand index 85480046d68..03ebd588cae 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+China/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+China/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; cloud-init status --wait > /dev/null 2>&1; [ $? -ne 0 ] && echo 'cloud-init failed' >> ${PROVISION_OUTPUT} && exit 1; echo "cloud-init succeeded" >> ${PROVISION_OUTPUT}; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+Containerd+MIG/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+Containerd+MIG/generatedCSECommand index 85480046d68..5b7f13031f9 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+Containerd+MIG/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+Containerd+MIG/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; LOCATION="southcentralus" /opt/azure/containers/init-aks-cloud.sh >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomCloud/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomCloud/generatedCSECommand index 85480046d68..a4df89c1447 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomCloud/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomCloud/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; cloud-init status --wait > /dev/null 2>&1; [ $? -ne 0 ] && echo 'cloud-init failed' >> ${PROVISION_OUTPUT} && exit 1; echo "cloud-init succeeded" >> ${PROVISION_OUTPUT}; REPO_DEPOT_ENDPOINT="" /opt/azure/containers/init-aks-cloud.sh >> /var/log/azure/cluster-provision.log 2>&1; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomOSConfig/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomOSConfig/generatedCSECommand index 85480046d68..03ebd588cae 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomOSConfig/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+CustomOSConfig/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; cloud-init status --wait > /dev/null 2>&1; [ $? -ne 0 ] && echo 'cloud-init failed' >> ${PROVISION_OUTPUT} && exit 1; echo "cloud-init succeeded" >> ${PROVISION_OUTPUT}; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS+HostsPlugin/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS+HostsPlugin/generatedCSECommand index 85480046d68..f73190d544c 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS+HostsPlugin/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS+HostsPlugin/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS/generatedCSECommand index 85480046d68..f73190d544c 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+LocalDNS/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AKSUbuntu2204+SSHStatusOn/generatedCSECommand b/aks-node-controller/parser/testdata/AKSUbuntu2204+SSHStatusOn/generatedCSECommand index 85480046d68..03ebd588cae 100644 --- a/aks-node-controller/parser/testdata/AKSUbuntu2204+SSHStatusOn/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AKSUbuntu2204+SSHStatusOn/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; cloud-init status --wait > /dev/null 2>&1; [ $? -ne 0 ] && echo 'cloud-init failed' >> ${PROVISION_OUTPUT} && exit 1; echo "cloud-init succeeded" >> ${PROVISION_OUTPUT}; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/AzureLinuxv2+Kata+DisableUnattendedUpgrades=false/generatedCSECommand b/aks-node-controller/parser/testdata/AzureLinuxv2+Kata+DisableUnattendedUpgrades=false/generatedCSECommand index 85480046d68..03ebd588cae 100644 --- a/aks-node-controller/parser/testdata/AzureLinuxv2+Kata+DisableUnattendedUpgrades=false/generatedCSECommand +++ b/aks-node-controller/parser/testdata/AzureLinuxv2+Kata+DisableUnattendedUpgrades=false/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; cloud-init status --wait > /dev/null 2>&1; [ $? -ne 0 ] && echo 'cloud-init failed' >> ${PROVISION_OUTPUT} && exit 1; echo "cloud-init succeeded" >> ${PROVISION_OUTPUT}; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file diff --git a/aks-node-controller/parser/testdata/Compatibility+EmptyConfig/generatedCSECommand b/aks-node-controller/parser/testdata/Compatibility+EmptyConfig/generatedCSECommand index 85480046d68..fd55aaa5299 100644 --- a/aks-node-controller/parser/testdata/Compatibility+EmptyConfig/generatedCSECommand +++ b/aks-node-controller/parser/testdata/Compatibility+EmptyConfig/generatedCSECommand @@ -1 +1 @@ -/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; if [ -f "${INIT_AKS_CLOUD_FILEPATH}" ]; then "${INIT_AKS_CLOUD_FILEPATH}" >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; fi; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file +/bin/bash -c echo $(date),$(hostname) > ${PROVISION_OUTPUT}; LOCATION="" /opt/azure/containers/init-aks-cloud.sh >> /var/log/azure/cluster-provision.log 2>&1 || exit $?; /usr/bin/nohup /bin/bash -c "/bin/bash /opt/azure/containers/provision_start.sh" \ No newline at end of file From 713030c3572dd5508012fa3d2b3e23d0ec0ece06 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Fri, 18 Sep 2026 11:20:58 -0400 Subject: [PATCH 47/60] Fix arm64 COSI convert azcopy download (azureedge.net CDN retired) azcopyvnext.azureedge.net no longer resolves after the Edgio azureedge.net CDN retirement, breaking the arm64 convert bootstrap. Repoint the azcopy download to GitHub releases; same v10.22.1 and same pinned SHA256 (tarballs are byte-identical, checksum verification unchanged). --- .pipelines/templates/.builder-cosi-convert-template.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index ffed55883c7..89039a7ca59 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -110,13 +110,14 @@ steps: # azcopy static binary. if ! command -v azcopy >/dev/null 2>&1; then + # azureedge.net CDN was retired; azcopy releases are served from GitHub. case "$(uname -m)" in aarch64|arm64) - AZCOPY_URL=https://azcopyvnext.azureedge.net/releases/release-10.22.1-20231220/azcopy_linux_arm64_10.22.1.tar.gz + AZCOPY_URL=https://github.com/Azure/azure-storage-azcopy/releases/download/v10.22.1/azcopy_linux_arm64_10.22.1.tar.gz AZCOPY_SHA256=4db9a4b48abc7775f1a5d6d928afc42361dcc57bbfcde23ac82e4c419a0dc8fc ;; *) - AZCOPY_URL=https://azcopyvnext.azureedge.net/releases/release-10.22.1-20231220/azcopy_linux_amd64_10.22.1.tar.gz + AZCOPY_URL=https://github.com/Azure/azure-storage-azcopy/releases/download/v10.22.1/azcopy_linux_amd64_10.22.1.tar.gz AZCOPY_SHA256=7549424d56ab2d8b4033c84c2a9bb167dc2dcbb23998acd7fffb37bc1a71a267 ;; esac From 1c2c9101559c0fce91c4cc4147060e1f37c869a1 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 21 Sep 2026 10:21:22 -0400 Subject: [PATCH 48/60] Fix conditional access policy error --- .pipelines/.vsts-vhd-builder-release.yaml | 2 + .../.builder-cosi-convert-template.yaml | 22 +---------- .../templates/.builder-release-template.yaml | 38 ++++++++++++++++++- .../scripts/convert-vhd-to-cosi.sh | 13 +++++-- 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index 6da9c3222e6..24cf5ca02e0 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -931,6 +931,7 @@ stages: name: $(NODE_SIG_TME_POOL_ARM64_NAME) variables: CAPTURED_SIG_VERSION: $[ dependencies.buildaclarm64tlgen2.outputs['exportCapturedSigVersion.CAPTURED_SIG_VERSION'] ] + VHD_SAS_URL: $[ dependencies.buildaclarm64tlgen2.outputs['exportVhdSas.VHD_SAS_URL'] ] steps: - bash: | echo '##vso[task.setvariable variable=OS_SKU]AzureContainerLinux' @@ -960,6 +961,7 @@ stages: name: $(NODE_SIG_TME_POOL_ARM64_NAME) variables: CAPTURED_SIG_VERSION: $[ dependencies.buildaclarm64fipstlgen2.outputs['exportCapturedSigVersion.CAPTURED_SIG_VERSION'] ] + VHD_SAS_URL: $[ dependencies.buildaclarm64fipstlgen2.outputs['exportVhdSas.VHD_SAS_URL'] ] steps: - bash: | echo '##vso[task.setvariable variable=OS_SKU]AzureContainerLinux' diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index 89039a7ca59..ae92b440a1b 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -201,6 +201,7 @@ steps: IMG_CUSTOMIZER_ALLOW_FALLBACK: "true" AFD_DOWNLOAD_HOSTNAME: $(AFD_DOWNLOAD_HOSTNAME) COSI_CONTAINER: $(COSI_CONTAINER) + VHD_SAS_URL: $(VHD_SAS_URL) - task: AzureCLI@2 inputs: @@ -221,24 +222,3 @@ steps: inputs: artifactName: 'cosi-publishing-info-${{ parameters.artifactName }}' targetPath: 'cosi-publishing-info.json' - - - task: AzureCLI@2 - inputs: - azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) - scriptType: bash - scriptLocation: inlineScript - inlineScript: | - echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd to immutable storage container" - export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" - export AZCOPY_CONCURRENCY_VALUE="AUTO" - az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "${CAPTURED_SIG_VERSION}.vhd" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --auth-mode login || exit 1 - echo "Successfully copied to immutable container" - # Remove old VHD from staging container after copy to immutable container is complete - azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --recursive=true - condition: | - and( - succeeded(), - eq(variables.ENVIRONMENT, 'tme'), - eq(variables.GENERATE_PUBLISHING_INFO, 'True') - ) - displayName: Copy to Immutable Storage Container diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index d55160de9c9..f2c8bf10ab0 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -540,8 +540,9 @@ steps: echo "Successfully copied to immutable container" # Remove old VHD from staging container after copy to immutable container is complete azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" --recursive=true - # arm64 ACL keeps its staged VHD for the separate native-arm64 COSI convert - # job to download; that job performs the immutable copy after conversion. + # arm64 ACL is handled by the dedicated step below (immutable copy + SAS + # handoff on this x64 pool) because its native-arm64 convert pool is + # Conditional-Access-blocked from acquiring storage tokens. condition: | and( succeeded(), @@ -555,6 +556,39 @@ steps: ) displayName: Copy to Immutable Storage Container + # arm64 ACL: its native-arm64 convert pool is Conditional-Access-blocked from + # acquiring storage tokens, so copy the VHD to the immutable container and drop + # the staging copy here (this x64 pool has storage auth), then hand the convert + # job a short read-only SAS so it downloads the VHD with no storage login. + - task: AzureCLI@2 + inputs: + azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + set -euo pipefail + export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" + export AZCOPY_CONCURRENCY_VALUE="AUTO" + vhd_name="${CAPTURED_SIG_VERSION}.vhd" + echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${vhd_name} to immutable container ${VHD_CONTAINER_NAME}" + az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "$vhd_name" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${vhd_name}" --auth-mode login + # Short read-only user-delegation SAS on the immutable copy for the arm64 convert job. + expiry="$(date -u -d '+8 hours' '+%Y-%m-%dT%H:%MZ')" + sas_url="$(az storage blob generate-sas --account-name "$STORAGE_ACCOUNT_NAME" --container-name "$VHD_CONTAINER_NAME" --name "$vhd_name" --permissions r --expiry "$expiry" --https-only --as-user --auth-mode login --full-uri -o tsv)" + echo "##vso[task.setvariable variable=VHD_SAS_URL;isOutput=true;issecret=true]$sas_url" + echo "Removing staging copy of ${vhd_name}" + azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${vhd_name}" --recursive=true + name: exportVhdSas + condition: | + and( + succeeded(), + eq(variables.ENVIRONMENT, 'tme'), + eq(variables.GENERATE_PUBLISHING_INFO, 'True'), + eq(variables['OS_SKU'], 'AzureContainerLinux'), + eq(variables['ARCHITECTURE'], 'ARM64') + ) + displayName: Copy arm64 ACL VHD to Immutable + mint convert SAS + - task: DownloadPipelineArtifact@2 displayName: Download Build Performance Program condition: and(succeeded(), eq(variables.ENVIRONMENT, 'tme')) diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index 550f5d48666..322a0d05c1c 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -83,16 +83,23 @@ LOCAL_COSI="$WORK_DIR/out/${CAPTURED_SIG_VERSION}.cosi" COSI_NAME="${CAPTURED_SIG_VERSION}.cosi" COSI_DOWNLOAD_URL="https://${AFD_DOWNLOAD_HOSTNAME}/${COSI_CONTAINER}/${COSI_NAME}" +# VHD_SAS_URL is set by the build job for the arm64 convert, whose pool has no +# storage-token path; downloading via the SAS URL needs no Azure AD login. The +# x64 convert leaves it unset and authenticates azcopy via the Azure CLI login. +DOWNLOAD_SRC="${VHD_SAS_URL:-$VHD_BLOB_URL}" + echo "Setting azcopy environment variables" -export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" export AZCOPY_CONCURRENCY_VALUE="AUTO" export AZCOPY_LOG_LOCATION="$WORK_DIR/azcopy-log-files/" export AZCOPY_JOB_PLAN_LOCATION="$WORK_DIR/azcopy-job-plan-files/" +if [ -z "${VHD_SAS_URL:-}" ]; then + export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" +fi mkdir -p "${AZCOPY_LOG_LOCATION}" mkdir -p "${AZCOPY_JOB_PLAN_LOCATION}" -echo "Downloading VHD from ${VHD_BLOB_URL}" -if azcopy copy "$VHD_BLOB_URL" "$LOCAL_VHD" --recursive=true; then +echo "Downloading VHD ${CAPTURED_SIG_VERSION}.vhd" +if azcopy copy "$DOWNLOAD_SRC" "$LOCAL_VHD" --recursive=true; then echo "Downloaded VHD to ${LOCAL_VHD}" else azExitCode=$? From e286b99eab06824f13f595f7ac38956ea508e1c3 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 21 Sep 2026 11:44:09 -0400 Subject: [PATCH 49/60] Fix COSI uploader Go compatibility and retry CUE setup --- .../.builder-cosi-convert-template.yaml | 6 +- .../templates/.builder-release-template.yaml | 3 +- packer.mk | 9 ++- spec/packer_spec.sh | 56 +++++++++++++++++++ spec/shellspec.Dockerfile | 2 +- 5 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 spec/packer_spec.sh diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index ae92b440a1b..6fe70fc3ec7 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -60,10 +60,8 @@ steps: # Baseline utilities used by scripts and downloads below. pkg_install ca-certificates curl tar gnupg make jq - # cosi-upload is built with `GOEXPERIMENT=ms_nocgo_opensslcrypto CGO_ENABLED=0` - # (see packer.mk build-cosi-upload), matching every other Go binary in this - # repo. That keeps FIPS/OpenSSL crypto compliance while avoiding a build-time - # C toolchain, so no gcc/binutils/libc/OpenSSL-dev packages are needed here. + # cosi-upload uses Microsoft's cgo-less OpenSSL backend (see packer.mk), + # so no build-time C toolchain or OpenSSL development packages are needed. # git isn't used by the convert scripts, but install it so any implicit # agent-side cleanup checkouts don't tank the job. diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index f2c8bf10ab0..b7b25431a89 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -86,7 +86,7 @@ steps: downloadPath: $(Build.SourcesDirectory) - bash: | - set -x + set -ex GOPATH="$(go env GOPATH)" echo "GOPATH is currently set to $GOPATH" export PATH="$PATH:$GOPATH/bin" @@ -95,6 +95,7 @@ steps: popd cue export ./schemas/manifest.cue > ./parts/linux/cloud-init/artifacts/manifest.json displayName: Setup Cue + retryCountOnTaskFailure: 3 - bash: | SKU_NAME=${OS_VERSION} && \ diff --git a/packer.mk b/packer.mk index 20a9bf556a5..4d9affd17ea 100755 --- a/packer.mk +++ b/packer.mk @@ -107,10 +107,17 @@ convert-sig-to-classic-storage-account-blob: az-login convert-vhd-to-cosi: az-login @./vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh "$${IMG_CUSTOMIZER_ALLOW_FALLBACK:-false}" +# Go 1.27 enables cgo-less OpenSSL by default and rejects the old experiment. build-cosi-upload: @echo "Building cosi-upload binary" @mkdir -p bin - @GOEXPERIMENT=ms_nocgo_opensslcrypto CGO_ENABLED=0 go build -o bin/cosi-upload ./cmd/cosi-upload + @go_experiment=""; \ + if GOEXPERIMENT=ms_nocgo_opensslcrypto go env GOEXPERIMENT >/dev/null 2>&1; then \ + go_experiment=ms_nocgo_opensslcrypto; \ + fi; \ + GOEXPERIMENT="$$go_experiment" CGO_ENABLED=0 go build -o bin/cosi-upload ./cmd/cosi-upload + @go version -m bin/cosi-upload | grep -q 'microsoft_systemcrypto=1$$' || \ + { echo "cosi-upload must be built with Microsoft Go system crypto" >&2; exit 1; } upload-cosi-to-pmc: build-cosi-upload @./vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh diff --git a/spec/packer_spec.sh b/spec/packer_spec.sh new file mode 100644 index 00000000000..d1e266320b3 --- /dev/null +++ b/spec/packer_spec.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +Describe 'build-cosi-upload' + setup() { + export MOCK_GO_VERSION=1.27 + export MOCK_SYSTEM_CRYPTO=1 + export MOCK_BUILD_STATUS=0 + + go() { + case "$1 $2" in + 'env GOEXPERIMENT') + [ "$MOCK_GO_VERSION" = 1.26 ] + ;; + 'build -o') + printf 'GOEXPERIMENT=%s CGO_ENABLED=%s\n' "${GOEXPERIMENT-}" "${CGO_ENABLED-}" + return "$MOCK_BUILD_STATUS" + ;; + 'version -m') + printf '\tbuild\tmicrosoft_systemcrypto=%s\n' "$MOCK_SYSTEM_CRYPTO" + ;; + *) return 1 ;; + esac + } + export -f go + } + BeforeEach 'setup' + + It 'uses the OpenSSL experiment on Go 1.26' + MOCK_GO_VERSION=1.26 + When run make --no-print-directory -f packer.mk SHELL=/bin/bash build-cosi-upload + The status should be success + The output should include 'GOEXPERIMENT=ms_nocgo_opensslcrypto CGO_ENABLED=0' + End + + It 'uses default system crypto on Go 1.27' + When run make --no-print-directory -f packer.mk SHELL=/bin/bash build-cosi-upload + The status should be success + The output should include 'GOEXPERIMENT= CGO_ENABLED=0' + End + + It 'rejects a binary without system crypto' + MOCK_SYSTEM_CRYPTO=0 + When run make --no-print-directory -f packer.mk SHELL=/bin/bash build-cosi-upload + The status should be failure + The output should include 'Building cosi-upload binary' + The error should include 'cosi-upload must be built with Microsoft Go system crypto' + End + + It 'propagates a compiler failure' + MOCK_BUILD_STATUS=17 + When run make --no-print-directory -f packer.mk SHELL=/bin/bash build-cosi-upload + The status should be failure + The output should include 'Building cosi-upload binary' + The error should include 'Error 17' + End +End \ No newline at end of file diff --git a/spec/shellspec.Dockerfile b/spec/shellspec.Dockerfile index a8c98177361..629ea330c73 100644 --- a/spec/shellspec.Dockerfile +++ b/spec/shellspec.Dockerfile @@ -4,7 +4,7 @@ FROM aksdataplanedev.azurecr.io/shellspec/shellspec-debian:0.28.1 RUN sed -i -e 's/\(deb\|security\).debian.org/archive.debian.org/g' /etc/apt/sources.list && \ apt-get update && \ - apt-get install -y --no-install-recommends gawk jq curl dnsutils && \ + apt-get install -y --no-install-recommends gawk jq curl dnsutils make && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* COPY ./ /src From 8a14746029a6b0102eb9897353b0c1892eeb4913 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 21 Sep 2026 20:42:15 -0400 Subject: [PATCH 50/60] Wait for VHD copy --- .../templates/.builder-release-template.yaml | 13 +-- .../convert_vhd_to_cosi_spec.sh | 105 ++++++++++++++++++ .../imagecustomizer/scripts/export-vhd-sas.sh | 36 ++++++ 3 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index b7b25431a89..91d5b392b51 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -567,18 +567,7 @@ steps: scriptType: bash scriptLocation: inlineScript inlineScript: | - set -euo pipefail - export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" - export AZCOPY_CONCURRENCY_VALUE="AUTO" - vhd_name="${CAPTURED_SIG_VERSION}.vhd" - echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${vhd_name} to immutable container ${VHD_CONTAINER_NAME}" - az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" --destination-blob "$vhd_name" --destination-container "$VHD_CONTAINER_NAME" --source-uri "${DESTINATION_STORAGE_CONTAINER}/${vhd_name}" --auth-mode login - # Short read-only user-delegation SAS on the immutable copy for the arm64 convert job. - expiry="$(date -u -d '+8 hours' '+%Y-%m-%dT%H:%MZ')" - sas_url="$(az storage blob generate-sas --account-name "$STORAGE_ACCOUNT_NAME" --container-name "$VHD_CONTAINER_NAME" --name "$vhd_name" --permissions r --expiry "$expiry" --https-only --as-user --auth-mode login --full-uri -o tsv)" - echo "##vso[task.setvariable variable=VHD_SAS_URL;isOutput=true;issecret=true]$sas_url" - echo "Removing staging copy of ${vhd_name}" - azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${vhd_name}" --recursive=true + bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh name: exportVhdSas condition: | and( diff --git a/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh b/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh index 249e82f8337..aed945f5dcc 100644 --- a/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh +++ b/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh @@ -83,3 +83,108 @@ Describe 'generate_cosi_package_version' End End End + +Describe 'VHD SAS handoff' + setup_handoff() { + export DESTINATION_STORAGE_CONTAINER=https://storage.invalid/staging + export CAPTURED_SIG_VERSION=test + export STORAGE_ACCOUNT_NAME=mock + export VHD_CONTAINER_NAME=immutable + export MOCK_COPY_STATUS=success + export MOCK_COMPLETE_AFTER_WAIT=0 + export MOCK_START_STATUS=0 + export MOCK_SHOW_STATUS=0 + export MOCK_SAS_STATUS=0 + + az() { + case "$*" in + 'storage blob copy start '*) + echo COPY_STARTED + return "$MOCK_START_STATUS" + ;; + 'storage blob show '*) + printf '%s\n' "$MOCK_COPY_STATUS" + return "$MOCK_SHOW_STATUS" + ;; + 'storage blob generate-sas '*) + echo MOCK_SAS + return "$MOCK_SAS_STATUS" + ;; + *) return 1 ;; + esac + } + azcopy() { echo SOURCE_REMOVED; } + sleep() { + [ "$1" = 15 ] || return 1 + echo COPY_WAITED + if [ "$MOCK_COMPLETE_AFTER_WAIT" = 1 ]; then + MOCK_COPY_STATUS=success + fi + } + export -f az azcopy sleep + } + BeforeEach 'setup_handoff' + + It 'waits for a successful copy before exporting the secret SAS and deleting the source' + MOCK_COPY_STATUS=pending + MOCK_COMPLETE_AFTER_WAIT=1 + When run bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + The status should be success + The line 3 of output should equal COPY_WAITED + The line 4 of output should equal '##vso[task.setvariable variable=VHD_SAS_URL;isOutput=true;issecret=true]MOCK_SAS' + The line 6 of output should equal SOURCE_REMOVED + End + + Describe 'unsuccessful copy states' + Parameters + failed + aborted + # shellcheck disable=SC2286 + '' + End + It 'preserves the source when the copy is not successful' + MOCK_COPY_STATUS="$1" + When run bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + The status should be failure + The output should include 'VHD copy to immutable container finished with status' + The output should not include MOCK_SAS + The output should not include SOURCE_REMOVED + End + End + + It 'preserves the source when the copy stays pending until timeout' + MOCK_COPY_STATUS=pending + When run bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + The status should be failure + The output should include 'Timed out waiting for the immutable VHD copy' + The output should not include MOCK_SAS + The output should not include SOURCE_REMOVED + End + + It 'stops when starting the copy fails' + MOCK_START_STATUS=1 + When run bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + The status should be failure + The output should include COPY_STARTED + The output should not include COPY_WAITED + The output should not include SOURCE_REMOVED + End + + It 'preserves the source when reading the copy status fails' + MOCK_SHOW_STATUS=1 + When run bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + The status should be failure + The output should include COPY_STARTED + The output should not include MOCK_SAS + The output should not include SOURCE_REMOVED + End + + It 'preserves the source when SAS minting fails' + MOCK_SAS_STATUS=1 + When run bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + The status should be failure + The output should include COPY_STARTED + The output should not include MOCK_SAS + The output should not include SOURCE_REMOVED + End +End diff --git a/vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh b/vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh new file mode 100644 index 00000000000..381c07131ee --- /dev/null +++ b/vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -euo pipefail + +export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" +export AZCOPY_CONCURRENCY_VALUE="AUTO" +vhd_name="${CAPTURED_SIG_VERSION}.vhd" + +echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${vhd_name} to immutable container ${VHD_CONTAINER_NAME}" +az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" \ + --destination-blob "$vhd_name" --destination-container "$VHD_CONTAINER_NAME" \ + --source-uri "${DESTINATION_STORAGE_CONTAINER}/${vhd_name}" --auth-mode login +for ((attempt = 0; attempt < 120; attempt++)); do + copy_status="$(az storage blob show --account-name "$STORAGE_ACCOUNT_NAME" \ + --container-name "$VHD_CONTAINER_NAME" --name "$vhd_name" --auth-mode login \ + --query properties.copy.status --output tsv)" + case "$copy_status" in + success) break ;; + pending) sleep 15 ;; + *) + echo "##vso[task.logissue type=error]VHD copy to immutable container finished with status ${copy_status}" + exit 1 + ;; + esac +done +if [ "$copy_status" != "success" ]; then + echo "##vso[task.logissue type=error]Timed out waiting for the immutable VHD copy" + exit 1 +fi + +expiry="$(date -u -d '+8 hours' '+%Y-%m-%dT%H:%MZ')" +sas_url="$(az storage blob generate-sas --account-name "$STORAGE_ACCOUNT_NAME" \ + --container-name "$VHD_CONTAINER_NAME" --name "$vhd_name" --permissions r \ + --expiry "$expiry" --https-only --as-user --auth-mode login --full-uri --output tsv)" +echo "##vso[task.setvariable variable=VHD_SAS_URL;isOutput=true;issecret=true]$sas_url" +echo "Removing staging copy of ${vhd_name}" +azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${vhd_name}" --recursive=true \ No newline at end of file From 839c50c99371cda017bd17ff746a7f505b9c0e15 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 21 Sep 2026 20:49:03 -0400 Subject: [PATCH 51/60] Gate COSI publishing --- .pipelines/.vsts-vhd-builder-release.yaml | 2 + .../templates/.builder-release-template.yaml | 113 +++++++++--------- 2 files changed, 60 insertions(+), 55 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index 1c35d38215e..767f2a69122 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -894,6 +894,7 @@ stages: useOverrides: ${{ parameters.useOverrides }} overrideBranch: ${{ parameters.overrideBranch }} artifactName: acl-tl-gen2 + enableCosiPublishing: true - job: buildaclarm64tlgen2 condition: eq('${{ parameters.buildaclarm64tlgen2 }}', true) dependsOn: [ ] @@ -950,6 +951,7 @@ stages: useOverrides: ${{ parameters.useOverrides }} overrideBranch: ${{ parameters.overrideBranch }} artifactName: acl-fips-tl-gen2 + enableCosiPublishing: true - job: buildaclarm64fipstlgen2 condition: eq('${{ parameters.buildaclarm64fipstlgen2 }}', true) dependsOn: [ ] diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index a54c1bb057f..b3ce08a0c0f 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -8,6 +8,9 @@ parameters: - name: overrideBranch type: string default: master + - name: enableCosiPublishing + type: boolean + default: false steps: - template: ./.template-override-components-json.yaml @@ -467,61 +470,61 @@ steps: env: RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) - - task: AzureCLI@2 - inputs: - azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) - scriptType: bash - scriptLocation: inlineScript - inlineScript: | - make -f packer.mk convert-vhd-to-cosi - # arm64 ACL runs this on the native arm64 pool as a separate job (see - # convertaclarm64* jobs); it can't run under emulation here. - condition: | - and( - succeeded(), - eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux'), - ne(variables['ARCHITECTURE'], 'ARM64') - ) - displayName: Convert ACL VHD to COSI - env: - RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) - OFFER_NAME: $(OS_SKU) - IMG_CUSTOMIZER_ALLOW_FALLBACK: "true" - AFD_DOWNLOAD_HOSTNAME: $(AFD_DOWNLOAD_HOSTNAME) - COSI_CONTAINER: $(COSI_CONTAINER) - - - task: AzureCLI@2 - inputs: - azureSubscription: $(COSI_PMC_SERVICE_CONNECTION) - scriptType: bash - scriptLocation: inlineScript - inlineScript: | - make -f packer.mk upload-cosi-to-pmc - condition: | - and( - succeeded(), - eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux'), - ne(variables['ARCHITECTURE'], 'ARM64') - ) - displayName: Upload COSI to PMC - env: - AFD_UPLOAD_ENDPOINT: $(AFD_UPLOAD_ENDPOINT) - COSI_CONTAINER: $(COSI_CONTAINER) - - - task: PublishPipelineArtifact@1 - condition: | - and( - succeeded(), - eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux'), - ne(variables['ARCHITECTURE'], 'ARM64') - ) - displayName: Publish COSI Publishing Info - inputs: - artifactName: 'cosi-publishing-info-${{ parameters.artifactName }}' - targetPath: 'cosi-publishing-info.json' + - ${{ if parameters.enableCosiPublishing }}: + - task: AzureCLI@2 + inputs: + azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + make -f packer.mk convert-vhd-to-cosi + # ARM64 conversion runs in the separate native-arm64 jobs. + condition: | + and( + succeeded(), + eq(variables.GENERATE_PUBLISHING_INFO, 'True'), + eq(variables['OS_SKU'], 'AzureContainerLinux'), + ne(variables['ARCHITECTURE'], 'ARM64') + ) + displayName: Convert ACL VHD to COSI + env: + RESOURCE_GROUP_NAME: $(AZURE_RESOURCE_GROUP_NAME) + OFFER_NAME: $(OS_SKU) + IMG_CUSTOMIZER_ALLOW_FALLBACK: "true" + AFD_DOWNLOAD_HOSTNAME: $(AFD_DOWNLOAD_HOSTNAME) + COSI_CONTAINER: $(COSI_CONTAINER) + + - task: AzureCLI@2 + inputs: + azureSubscription: $(COSI_PMC_SERVICE_CONNECTION) + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + make -f packer.mk upload-cosi-to-pmc + condition: | + and( + succeeded(), + eq(variables.GENERATE_PUBLISHING_INFO, 'True'), + eq(variables['OS_SKU'], 'AzureContainerLinux'), + ne(variables['ARCHITECTURE'], 'ARM64') + ) + displayName: Upload COSI to PMC + env: + AFD_UPLOAD_ENDPOINT: $(AFD_UPLOAD_ENDPOINT) + COSI_CONTAINER: $(COSI_CONTAINER) + + - task: PublishPipelineArtifact@1 + condition: | + and( + succeeded(), + eq(variables.GENERATE_PUBLISHING_INFO, 'True'), + eq(variables['OS_SKU'], 'AzureContainerLinux'), + ne(variables['ARCHITECTURE'], 'ARM64') + ) + displayName: Publish COSI Publishing Info + inputs: + artifactName: 'cosi-publishing-info-${{ parameters.artifactName }}' + targetPath: 'cosi-publishing-info.json' - task: AzureCLI@2 From f252bee222f968fbd7ff6bc7a38d36d13fd4f071 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:12:39 -0400 Subject: [PATCH 52/60] Match VHD source --- .../templates/.builder-cosi-convert-template.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index 6fe70fc3ec7..b559e5b467f 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -180,9 +180,14 @@ steps: displayName: Set SKU Name - bash: | - echo "##vso[task.setvariable variable=DESTINATION_STORAGE_CONTAINER]${CLASSIC_BLOB_STAGING}" - echo "DESTINATION_STORAGE_CONTAINER is set to ${CLASSIC_BLOB_STAGING}" - displayName: Set Destination Storage Container to Staging + set -euo pipefail + if [ "${ENVIRONMENT,,}" = "tme" ]; then + container="${CLASSIC_BLOB_STAGING}" + else + container="${CLASSIC_BLOB}" + fi + echo "##vso[task.setvariable variable=DESTINATION_STORAGE_CONTAINER]${container}" + displayName: Set Destination Storage Container - task: AzureCLI@2 inputs: From e7f57930127faa9c2a199cf195e973f8a2c64e5c Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:17:49 -0400 Subject: [PATCH 53/60] Scope COSI artifacts --- .../convert_vhd_to_cosi_spec.sh | 65 +++++++++++++++++++ .../scripts/convert-vhd-to-cosi.sh | 3 +- .../scripts/upload-cosi-to-pmc.sh | 3 +- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh b/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh index aed945f5dcc..2858d007709 100644 --- a/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh +++ b/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh @@ -188,3 +188,68 @@ Describe 'VHD SAS handoff' The output should not include SOURCE_REMOVED End End + +Describe 'COSI artifact names' + setup_artifacts() { + convert_script="$(pwd)/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh" + upload_script="$(pwd)/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh" + test_dir="$(mktemp -d)" + mkdir -p "$test_dir/bin" + ln -s /bin/echo "$test_dir/bin/cosi-upload" + export CAPTURED_SIG_VERSION=202609.21.0 + export IMAGE_VERSION=202609.21.0 + export DESTINATION_STORAGE_CONTAINER=https://storage.invalid/vhds + export IMG_CUSTOMIZER_CONTAINER=mock + export AFD_DOWNLOAD_HOSTNAME=download.invalid + export AFD_UPLOAD_ENDPOINT=https://upload.invalid + export COSI_CONTAINER=cosi + + azcopy() { return 0; } + docker() { + if [ "$1" = run ]; then + printf 'mock COSI\n' > "$PWD/cosi-convert/out/${CAPTURED_SIG_VERSION}.cosi" + fi + } + export -f azcopy docker + } + cleanup_artifacts() { rm -rf "$test_dir"; } + BeforeEach 'setup_artifacts' + AfterEach 'cleanup_artifacts' + + check_artifact_name() { + cd "$test_dir" || return 1 + bash "$convert_script" >/dev/null || return $? + jq -r .cosi_url cosi-publishing-info.json + bash "$upload_script" + } + + Describe 'variants with a shared capture version' + Parameters + aclgen2TL X86_64 false + aclgen2fipsTL X86_64 true + aclgen2arm64TL ARM64 false + aclgen2arm64fipsTL ARM64 true + End + It 'uses the SKU in both the metadata URL and upload destination' + export SKU_NAME="$1" ARCHITECTURE="$2" ENABLE_FIPS="$3" + When call check_artifact_name + The status should be success + The line 1 of output should equal "https://download.invalid/cosi/${SKU_NAME}-${CAPTURED_SIG_VERSION}.cosi" + The line 2 of output should include "--blob ${SKU_NAME}-${CAPTURED_SIG_VERSION}.cosi --file ${test_dir}/${SKU_NAME}-${CAPTURED_SIG_VERSION}.cosi" + End + End + + It 'rejects conversion without a SKU' + unset SKU_NAME + When run bash "$convert_script" + The status should be failure + The output should equal 'SKU_NAME was not set!' + End + + It 'rejects upload without a SKU' + unset SKU_NAME + When run bash "$upload_script" + The status should be failure + The output should equal 'SKU_NAME was not set!' + End +End diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index 322a0d05c1c..dfc98a02b36 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -47,6 +47,7 @@ ${__SOURCED__:+return} required_env_vars=( "DESTINATION_STORAGE_CONTAINER" "CAPTURED_SIG_VERSION" + "SKU_NAME" "IMG_CUSTOMIZER_CONTAINER" "AFD_DOWNLOAD_HOSTNAME" "COSI_CONTAINER" @@ -80,7 +81,7 @@ VHD_BLOB_URL="${DESTINATION_STORAGE_CONTAINER}/${CAPTURED_SIG_VERSION}.vhd" LOCAL_VHD="$WORK_DIR/${CAPTURED_SIG_VERSION}.vhd" LOCAL_COSI="$WORK_DIR/out/${CAPTURED_SIG_VERSION}.cosi" -COSI_NAME="${CAPTURED_SIG_VERSION}.cosi" +COSI_NAME="${SKU_NAME}-${CAPTURED_SIG_VERSION}.cosi" COSI_DOWNLOAD_URL="https://${AFD_DOWNLOAD_HOSTNAME}/${COSI_CONTAINER}/${COSI_NAME}" # VHD_SAS_URL is set by the build job for the arm64 convert, whose pool has no diff --git a/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh b/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh index 5431a485bdc..10f191d8202 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh @@ -9,6 +9,7 @@ set -euo pipefail required_env_vars=( "CAPTURED_SIG_VERSION" + "SKU_NAME" "AFD_UPLOAD_ENDPOINT" "COSI_CONTAINER" ) @@ -22,7 +23,7 @@ do done COSI_WORK_DIR="$(pwd)" -COSI_NAME="${CAPTURED_SIG_VERSION}.cosi" +COSI_NAME="${SKU_NAME}-${CAPTURED_SIG_VERSION}.cosi" STAGED_COSI="${COSI_WORK_DIR}/${COSI_NAME}" if [ ! -f "$STAGED_COSI" ]; then From 0d8bbca130810959b7ea8b789898f8b4036003e1 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:27:24 -0400 Subject: [PATCH 54/60] Check Go before convert --- .pipelines/templates/.builder-cosi-convert-template.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.pipelines/templates/.builder-cosi-convert-template.yaml b/.pipelines/templates/.builder-cosi-convert-template.yaml index b559e5b467f..eb971fbe7c0 100644 --- a/.pipelines/templates/.builder-cosi-convert-template.yaml +++ b/.pipelines/templates/.builder-cosi-convert-template.yaml @@ -134,8 +134,7 @@ steps: fi azcopy --version - # Go is needed to build cosi-upload. - # On apt-based agents, install the repo-pinned Go toolchain after sources are extracted. + # Azure Linux packages Microsoft Go; the preflight build rejects incompatible toolchains. if ! command -v go >/dev/null 2>&1; then if [ "$PKG" = apt ]; then echo "Go not found yet; will install pinned version after source extraction" @@ -165,6 +164,10 @@ steps: fi go version + - bash: make -f packer.mk build-cosi-upload + displayName: Build COSI uploader with Microsoft system crypto + condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True')) + - bash: | SKU_NAME=${OS_VERSION} && \ if [[ "${HYPERV_GENERATION,,}" == "v2" ]]; then SKU_NAME="${SKU_NAME}gen2"; fi && \ @@ -212,7 +215,7 @@ steps: scriptType: bash scriptLocation: inlineScript inlineScript: | - make -f packer.mk upload-cosi-to-pmc + bash ./vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True')) displayName: Upload COSI to PMC env: From de3a935f8b3d306bea274720307ec056bdc01cfc Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:40:56 -0400 Subject: [PATCH 55/60] Reuse VHD copies --- .../convert_vhd_to_cosi_spec.sh | 68 +++++++++++++++++++ .../imagecustomizer/scripts/export-vhd-sas.sh | 36 ++++++++-- 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh b/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh index 2858d007709..d63870edc3e 100644 --- a/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh +++ b/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh @@ -90,6 +90,9 @@ Describe 'VHD SAS handoff' export CAPTURED_SIG_VERSION=test export STORAGE_ACCOUNT_NAME=mock export VHD_CONTAINER_NAME=immutable + export MOCK_DESTINATION_EXISTS=false + export MOCK_SOURCE_EXISTS=true + export MOCK_EXISTS_STATUS=0 export MOCK_COPY_STATUS=success export MOCK_COMPLETE_AFTER_WAIT=0 export MOCK_START_STATUS=0 @@ -98,6 +101,13 @@ Describe 'VHD SAS handoff' az() { case "$*" in + 'storage blob exists --account-name '*) + printf '%s\n' "$MOCK_DESTINATION_EXISTS" + return "$MOCK_EXISTS_STATUS" + ;; + 'storage blob exists --blob-url '*) + printf '%s\n' "$MOCK_SOURCE_EXISTS" + ;; 'storage blob copy start '*) echo COPY_STARTED return "$MOCK_START_STATUS" @@ -135,6 +145,64 @@ Describe 'VHD SAS handoff' The line 6 of output should equal SOURCE_REMOVED End + It 'reuses a successful immutable copy on retry' + MOCK_DESTINATION_EXISTS=true + When run bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + The status should be success + The output should include MOCK_SAS + The output should include SOURCE_REMOVED + The output should not include COPY_STARTED + The output should not include COPY_WAITED + End + + It 'waits for an existing pending copy instead of overwriting it' + MOCK_DESTINATION_EXISTS=true + MOCK_COPY_STATUS=pending + MOCK_COMPLETE_AFTER_WAIT=1 + When run bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + The status should be success + The line 2 of output should equal COPY_WAITED + The line 3 of output should equal '##vso[task.setvariable variable=VHD_SAS_URL;isOutput=true;issecret=true]MOCK_SAS' + The output should include SOURCE_REMOVED + The output should not include COPY_STARTED + End + + It 'refreshes the SAS when the staging source is already removed' + MOCK_DESTINATION_EXISTS=true + MOCK_SOURCE_EXISTS=false + When run bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + The status should be success + The output should include MOCK_SAS + The output should include 'is already removed' + The output should not include COPY_STARTED + The output should not include SOURCE_REMOVED + End + + It 'stops when checking the destination fails' + MOCK_EXISTS_STATUS=1 + When run bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + The status should be failure + The output should be blank + End + + It 'rejects an unknown destination existence result' + MOCK_DESTINATION_EXISTS=unknown + When run bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + The status should be failure + The output should include 'Unable to determine whether the immutable VHD exists' + The output should not include COPY_STARTED + The output should not include MOCK_SAS + The output should not include SOURCE_REMOVED + End + + It 'does not delete a source with an unknown existence result' + MOCK_SOURCE_EXISTS=unknown + When run bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + The status should be failure + The output should include 'Unable to determine whether the staging VHD exists' + The output should not include SOURCE_REMOVED + End + Describe 'unsuccessful copy states' Parameters failed diff --git a/vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh b/vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh index 381c07131ee..7354318383f 100644 --- a/vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh @@ -4,11 +4,24 @@ set -euo pipefail export AZCOPY_AUTO_LOGIN_TYPE="AZCLI" export AZCOPY_CONCURRENCY_VALUE="AUTO" vhd_name="${CAPTURED_SIG_VERSION}.vhd" +source_uri="${DESTINATION_STORAGE_CONTAINER}/${vhd_name}" -echo "Copying ${DESTINATION_STORAGE_CONTAINER}/${vhd_name} to immutable container ${VHD_CONTAINER_NAME}" -az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" \ - --destination-blob "$vhd_name" --destination-container "$VHD_CONTAINER_NAME" \ - --source-uri "${DESTINATION_STORAGE_CONTAINER}/${vhd_name}" --auth-mode login +destination_exists="$(az storage blob exists --account-name "$STORAGE_ACCOUNT_NAME" \ + --container-name "$VHD_CONTAINER_NAME" --name "$vhd_name" --auth-mode login \ + --query exists --output tsv)" +case "${destination_exists,,}" in + false) + echo "Copying ${source_uri} to immutable container ${VHD_CONTAINER_NAME}" + az storage blob copy start --account-name "$STORAGE_ACCOUNT_NAME" \ + --destination-blob "$vhd_name" --destination-container "$VHD_CONTAINER_NAME" \ + --source-uri "$source_uri" --auth-mode login + ;; + true) echo "Immutable VHD ${vhd_name} already exists; checking copy status" ;; + *) + echo "##vso[task.logissue type=error]Unable to determine whether the immutable VHD exists" + exit 1 + ;; +esac for ((attempt = 0; attempt < 120; attempt++)); do copy_status="$(az storage blob show --account-name "$STORAGE_ACCOUNT_NAME" \ --container-name "$VHD_CONTAINER_NAME" --name "$vhd_name" --auth-mode login \ @@ -32,5 +45,16 @@ sas_url="$(az storage blob generate-sas --account-name "$STORAGE_ACCOUNT_NAME" \ --container-name "$VHD_CONTAINER_NAME" --name "$vhd_name" --permissions r \ --expiry "$expiry" --https-only --as-user --auth-mode login --full-uri --output tsv)" echo "##vso[task.setvariable variable=VHD_SAS_URL;isOutput=true;issecret=true]$sas_url" -echo "Removing staging copy of ${vhd_name}" -azcopy remove "${DESTINATION_STORAGE_CONTAINER}/${vhd_name}" --recursive=true \ No newline at end of file +source_exists="$(az storage blob exists --blob-url "$source_uri" --auth-mode login \ + --query exists --output tsv)" +case "${source_exists,,}" in + true) + echo "Removing staging copy of ${vhd_name}" + azcopy remove "$source_uri" --recursive=true + ;; + false) echo "Staging VHD ${vhd_name} is already removed" ;; + *) + echo "##vso[task.logissue type=error]Unable to determine whether the staging VHD exists" + exit 1 + ;; +esac \ No newline at end of file From ac3b59b572999c4d9968746b871f7dc265e54326 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:42:55 -0400 Subject: [PATCH 56/60] Test COSI uploads --- cmd/cosi-upload/main.go | 24 ++++--- cmd/cosi-upload/main_test.go | 134 +++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 cmd/cosi-upload/main_test.go diff --git a/cmd/cosi-upload/main.go b/cmd/cosi-upload/main.go index 872fce15eb4..078f51ca90a 100644 --- a/cmd/cosi-upload/main.go +++ b/cmd/cosi-upload/main.go @@ -48,20 +48,24 @@ func run(ctx context.Context, endpoint, container, blob, filePath string) error return fmt.Errorf("create blob client for %s: %w", endpoint, err) } - f, err := os.Open(filePath) + log.Printf("Uploading %s to %s/%s/%s", filePath, endpoint, container, blob) + if err := uploadFile(ctx, client, container, blob, filePath); err != nil { + return fmt.Errorf("upload %s -> %s/%s/%s: %w", filePath, endpoint, container, blob, err) + } + log.Printf("Successfully uploaded %s/%s/%s", endpoint, container, blob) + return nil +} + +func uploadFile(ctx context.Context, client *azblob.Client, container, blob, filePath string) error { + file, err := os.Open(filePath) if err != nil { return fmt.Errorf("open %s: %w", filePath, err) } - defer func() { _ = f.Close() }() + defer func() { _ = file.Close() }() - log.Printf("Uploading %s to %s/%s/%s", filePath, endpoint, container, blob) - if _, err := client.UploadFile(ctx, container, blob, f, &azblob.UploadFileOptions{ + _, err = client.UploadFile(ctx, container, blob, file, &azblob.UploadFileOptions{ BlockSize: 16 * 1024 * 1024, // 16 MiB blocks Concurrency: 8, - }); err != nil { - return fmt.Errorf("upload %s -> %s/%s/%s: %w", filePath, endpoint, container, blob, err) - } - - log.Printf("Successfully uploaded %s/%s/%s", endpoint, container, blob) - return nil + }) + return err } diff --git a/cmd/cosi-upload/main_test.go b/cmd/cosi-upload/main_test.go new file mode 100644 index 00000000000..1b7a79cff06 --- /dev/null +++ b/cmd/cosi-upload/main_test.go @@ -0,0 +1,134 @@ +package main + +import ( + "context" + "encoding/xml" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" + "github.com/stretchr/testify/require" +) + +type testCredential struct{} + +func (testCredential) GetToken(context.Context, policy.TokenRequestOptions) (azcore.AccessToken, error) { + return azcore.AccessToken{Token: "test-token", ExpiresOn: time.Now().Add(time.Hour)}, nil +} + +func newUploadTestClient(test *testing.T, handler http.HandlerFunc) *azblob.Client { + test.Helper() + server := httptest.NewTLSServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { + if request.Header.Get("Authorization") != "Bearer test-token" { + test.Error("upload request is missing its bearer token") + response.WriteHeader(http.StatusUnauthorized) + return + } + handler(response, request) + })) + test.Cleanup(server.Close) + client, err := azblob.NewClient(server.URL, testCredential{}, &azblob.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Transport: server.Client(), + Retry: policy.RetryOptions{MaxRetries: -1}, + }, + }) + require.NoError(test, err) + return client +} + +func TestUploadFileMultipart(test *testing.T) { + file, err := os.CreateTemp(test.TempDir(), "*.cosi") + require.NoError(test, err) + fileSize := int64(blockblob.MaxUploadBlobBytes + 1) + require.NoError(test, file.Truncate(fileSize)) + require.NoError(test, file.Close()) + + var mutex sync.Mutex + blocks := make(map[string]int64) + var committed struct { + Latest []string `xml:"Latest"` + } + client := newUploadTestClient(test, func(response http.ResponseWriter, request *http.Request) { + if request.Method != http.MethodPut || request.URL.Path != "/cosi/acl-arm64.cosi" { + test.Errorf("unexpected upload request: %s %s", request.Method, request.URL.Path) + response.WriteHeader(http.StatusBadRequest) + return + } + switch request.URL.Query().Get("comp") { + case "block": + size, copyErr := io.Copy(io.Discard, request.Body) + if copyErr != nil { + test.Error(copyErr) + response.WriteHeader(http.StatusInternalServerError) + return + } + mutex.Lock() + blocks[request.URL.Query().Get("blockid")] = size + mutex.Unlock() + case "blocklist": + mutex.Lock() + decodeErr := xml.NewDecoder(request.Body).Decode(&committed) + mutex.Unlock() + if decodeErr != nil { + test.Error(decodeErr) + response.WriteHeader(http.StatusBadRequest) + return + } + default: + test.Error("large file did not use multipart upload") + response.WriteHeader(http.StatusBadRequest) + return + } + response.WriteHeader(http.StatusCreated) + }) + + require.NoError(test, uploadFile(context.Background(), client, "cosi", "acl-arm64.cosi", file.Name())) + mutex.Lock() + defer mutex.Unlock() + require.Len(test, blocks, 17) + require.Len(test, committed.Latest, len(blocks)) + var totalBytes int64 + for _, blockID := range committed.Latest { + size, exists := blocks[blockID] + require.True(test, exists, "committed an unstaged block") + require.Positive(test, size) + require.LessOrEqual(test, size, int64(16*1024*1024)) + totalBytes += size + delete(blocks, blockID) + } + require.Equal(test, fileSize, totalBytes) + require.Empty(test, blocks) +} + +func TestUploadFileMissingFile(test *testing.T) { + client := newUploadTestClient(test, func(http.ResponseWriter, *http.Request) { + test.Error("missing file must not send an upload request") + }) + err := uploadFile(context.Background(), client, "cosi", "missing.cosi", filepath.Join(test.TempDir(), "missing.cosi")) + require.ErrorIs(test, err, os.ErrNotExist) +} + +func TestUploadFileHTTPError(test *testing.T) { + filePath := filepath.Join(test.TempDir(), "test.cosi") + require.NoError(test, os.WriteFile(filePath, []byte("test COSI"), 0600)) + client := newUploadTestClient(test, func(response http.ResponseWriter, request *http.Request) { + _, _ = io.Copy(io.Discard, request.Body) + response.Header().Set("x-ms-error-code", "AuthorizationPermissionMismatch") + response.WriteHeader(http.StatusForbidden) + }) + err := uploadFile(context.Background(), client, "cosi", "test.cosi", filePath) + var responseError *azcore.ResponseError + require.ErrorAs(test, err, &responseError) + require.Equal(test, http.StatusForbidden, responseError.StatusCode) + require.Equal(test, "AuthorizationPermissionMismatch", responseError.ErrorCode) +} From f86bcf562b0ecb201e4521334600cbf73352d75c Mon Sep 17 00:00:00 2001 From: "aks-node-assistant[bot]" <190555641+aks-node-assistant[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 01:43:52 +0000 Subject: [PATCH 57/60] chore: tidy go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 870a2d6cb4f..d3cee501d74 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/Azure/agentbaker go 1.26.0 require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 github.com/Azure/go-autorest/autorest/to v0.4.1 @@ -22,7 +23,6 @@ require ( ) require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect From c52bca202c55c8067fa6b015028eb02b83a24da6 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:01:50 -0400 Subject: [PATCH 58/60] Pin COSI inputs --- .pipelines/.vsts-vhd-builder-release.yaml | 26 ++++--- .pipelines/.vsts-vhd-builder.yaml | 16 ++-- .../templates/.builder-release-template.yaml | 8 ++ .../convert_vhd_to_cosi_spec.sh | 78 ++++++++++++++++++- .../scripts/convert-vhd-to-cosi.sh | 23 +++--- 5 files changed, 118 insertions(+), 33 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index 767f2a69122..2a8723310c5 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -886,8 +886,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=TRUSTED_LAUNCH_SUPPORTED]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5@sha256:b1a46447f67d73dce1a76163d3f8dbcbe12988b65c51393621529c52af160729' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0@sha256:c74e16c636e08486f517cd3b446e3577e52b24feea06aa245a8742a9f1c4a70a' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -915,8 +915,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=TRUSTED_LAUNCH_SUPPORTED]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5@sha256:b1a46447f67d73dce1a76163d3f8dbcbe12988b65c51393621529c52af160729' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0@sha256:c74e16c636e08486f517cd3b446e3577e52b24feea06aa245a8742a9f1c4a70a' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -943,8 +943,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=TRUSTED_LAUNCH_SUPPORTED]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5@sha256:b1a46447f67d73dce1a76163d3f8dbcbe12988b65c51393621529c52af160729' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0@sha256:c74e16c636e08486f517cd3b446e3577e52b24feea06aa245a8742a9f1c4a70a' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -972,8 +972,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=TRUSTED_LAUNCH_SUPPORTED]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5@sha256:b1a46447f67d73dce1a76163d3f8dbcbe12988b65c51393621529c52af160729' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0@sha256:c74e16c636e08486f517cd3b446e3577e52b24feea06aa245a8742a9f1c4a70a' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -992,6 +992,7 @@ stages: name: $(NODE_SIG_TME_POOL_ARM64_NAME) variables: CAPTURED_SIG_VERSION: $[ dependencies.buildaclarm64tlgen2.outputs['exportCapturedSigVersion.CAPTURED_SIG_VERSION'] ] + IMAGE_VERSION: $[ dependencies.buildaclarm64tlgen2.outputs['resolveCosiImageVersion.IMAGE_VERSION'] ] VHD_SAS_URL: $[ dependencies.buildaclarm64tlgen2.outputs['exportVhdSas.VHD_SAS_URL'] ] steps: - bash: | @@ -1008,8 +1009,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]false' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5@sha256:b1a46447f67d73dce1a76163d3f8dbcbe12988b65c51393621529c52af160729' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0@sha256:c74e16c636e08486f517cd3b446e3577e52b24feea06aa245a8742a9f1c4a70a' displayName: Setup Build Variables - template: ./templates/.builder-cosi-convert-template.yaml parameters: @@ -1022,6 +1023,7 @@ stages: name: $(NODE_SIG_TME_POOL_ARM64_NAME) variables: CAPTURED_SIG_VERSION: $[ dependencies.buildaclarm64fipstlgen2.outputs['exportCapturedSigVersion.CAPTURED_SIG_VERSION'] ] + IMAGE_VERSION: $[ dependencies.buildaclarm64fipstlgen2.outputs['resolveCosiImageVersion.IMAGE_VERSION'] ] VHD_SAS_URL: $[ dependencies.buildaclarm64fipstlgen2.outputs['exportVhdSas.VHD_SAS_URL'] ] steps: - bash: | @@ -1038,8 +1040,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_FIPS]True' echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5@sha256:b1a46447f67d73dce1a76163d3f8dbcbe12988b65c51393621529c52af160729' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0@sha256:c74e16c636e08486f517cd3b446e3577e52b24feea06aa245a8742a9f1c4a70a' displayName: Setup Build Variables - template: ./templates/.builder-cosi-convert-template.yaml parameters: diff --git a/.pipelines/.vsts-vhd-builder.yaml b/.pipelines/.vsts-vhd-builder.yaml index 46bbe6b9a09..d8d338e253c 100644 --- a/.pipelines/.vsts-vhd-builder.yaml +++ b/.pipelines/.vsts-vhd-builder.yaml @@ -257,8 +257,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=TRUSTED_LAUNCH_SUPPORTED]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5@sha256:b1a46447f67d73dce1a76163d3f8dbcbe12988b65c51393621529c52af160729' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0@sha256:c74e16c636e08486f517cd3b446e3577e52b24feea06aa245a8742a9f1c4a70a' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -282,8 +282,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=TRUSTED_LAUNCH_SUPPORTED]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5@sha256:b1a46447f67d73dce1a76163d3f8dbcbe12988b65c51393621529c52af160729' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0@sha256:c74e16c636e08486f517cd3b446e3577e52b24feea06aa245a8742a9f1c4a70a' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -307,8 +307,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=TRUSTED_LAUNCH_SUPPORTED]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5@sha256:b1a46447f67d73dce1a76163d3f8dbcbe12988b65c51393621529c52af160729' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0@sha256:c74e16c636e08486f517cd3b446e3577e52b24feea06aa245a8742a9f1c4a70a' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: @@ -332,8 +332,8 @@ stages: echo '##vso[task.setvariable variable=ENABLE_TRUSTED_LAUNCH]True' echo '##vso[task.setvariable variable=TRUSTED_LAUNCH_SUPPORTED]True' echo '##vso[task.setvariable variable=ENABLE_CGROUPV2]True' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5' - echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER]mcr.microsoft.com/azurelinux/imagecustomizer:1.5@sha256:b1a46447f67d73dce1a76163d3f8dbcbe12988b65c51393621529c52af160729' + echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0@sha256:c74e16c636e08486f517cd3b446e3577e52b24feea06aa245a8742a9f1c4a70a' displayName: Setup Build Variables - template: ./templates/.builder-release-template.yaml parameters: diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index b3ce08a0c0f..50b3e275b71 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -192,6 +192,14 @@ steps: condition: always() displayName: Set Post-Build Variables + - bash: | + image_version="${IMAGE_VERSION:-$(date -u +%Y%m.%d.0)}" + echo "##vso[task.setvariable variable=IMAGE_VERSION]${image_version}" + echo "##vso[task.setvariable variable=IMAGE_VERSION;isOutput=true]${image_version}" + name: resolveCosiImageVersion + displayName: Resolve COSI Image Version + condition: and(succeeded(), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables.GENERATE_PUBLISHING_INFO, 'True')) + # Expose the captured SIG version to the separate native-arm64 COSI convert job # (arm64 ACL COSI conversion can't run under emulation, so it runs on the arm64 # pool as its own job that consumes this output variable). diff --git a/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh b/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh index d63870edc3e..7bf1bbb746f 100644 --- a/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh +++ b/spec/vhdbuilder/packer/imagecustomizer/convert_vhd_to_cosi_spec.sh @@ -267,14 +267,20 @@ Describe 'COSI artifact names' export CAPTURED_SIG_VERSION=202609.21.0 export IMAGE_VERSION=202609.21.0 export DESTINATION_STORAGE_CONTAINER=https://storage.invalid/vhds - export IMG_CUSTOMIZER_CONTAINER=mock + export IMG_CUSTOMIZER_CONTAINER=registry.invalid/imagecustomizer@sha256:0000000000000000000000000000000000000000000000000000000000000000 + export IMG_CUSTOMIZER_CONTAINER_FALLBACK=registry.invalid/fallback@sha256:1111111111111111111111111111111111111111111111111111111111111111 + export EXPECTED_IMG_CUSTOMIZER_REF="$IMG_CUSTOMIZER_CONTAINER" + export MOCK_PRIMARY_PULL_FAIL=0 export AFD_DOWNLOAD_HOSTNAME=download.invalid export AFD_UPLOAD_ENDPOINT=https://upload.invalid export COSI_CONTAINER=cosi azcopy() { return 0; } docker() { - if [ "$1" = run ]; then + if [ "$1" = pull ] && [ "$4" = "$IMG_CUSTOMIZER_CONTAINER" ] && [ "$MOCK_PRIMARY_PULL_FAIL" = 1 ]; then + return 1 + elif [ "$1" = run ]; then + [ "$9" = "$EXPECTED_IMG_CUSTOMIZER_REF" ] || return 1 printf 'mock COSI\n' > "$PWD/cosi-convert/out/${CAPTURED_SIG_VERSION}.cosi" fi } @@ -286,11 +292,77 @@ Describe 'COSI artifact names' check_artifact_name() { cd "$test_dir" || return 1 - bash "$convert_script" >/dev/null || return $? + bash "$convert_script" "${1:-false}" >/dev/null || return $? jq -r .cosi_url cosi-publishing-info.json bash "$upload_script" } + check_build_version() { + cd "$test_dir" || return 1 + date() { printf '%s\n' 202610.01.0; } + export -f date + bash "$convert_script" >/dev/null || return $? + jq -r .image_version cosi-publishing-info.json + } + + It 'keeps the build version when conversion starts on a later UTC date' + export SKU_NAME=aclgen2arm64fipsTL ARCHITECTURE=ARM64 ENABLE_FIPS=true + When call check_build_version + The status should be success + The output should equal '202609.21.0-fips' + End + + It 'rejects conversion without the build-resolved version' + export SKU_NAME=aclgen2arm64TL + unset IMAGE_VERSION + When run bash "$convert_script" + The status should be failure + The output should equal 'IMAGE_VERSION was not set!' + End + + It 'rejects a mutable primary image before downloading or converting' + export SKU_NAME=aclgen2arm64TL IMG_CUSTOMIZER_CONTAINER=mcr.microsoft.com/azurelinux/imagecustomizer:1.5 + When run bash "$convert_script" + The status should be failure + The output should be blank + The error should include 'ImageCustomizer reference must be pinned with a sha256 digest' + End + + It 'rejects an enabled mutable fallback before downloading or converting' + export SKU_NAME=aclgen2arm64TL IMG_CUSTOMIZER_CONTAINER_FALLBACK=ghcr.io/microsoft/imagecustomizer:1.5.0 + When run bash "$convert_script" true + The status should be failure + The output should be blank + The error should include 'ImageCustomizer reference must be pinned with a sha256 digest' + End + + It 'rejects an enabled fallback without an approved digest' + export SKU_NAME=aclgen2arm64TL + unset IMG_CUSTOMIZER_CONTAINER_FALLBACK + When run bash "$convert_script" true + The status should be failure + The output should be blank + The error should include 'ImageCustomizer reference must be pinned with a sha256 digest' + End + + It 'runs the pinned fallback when the primary pull fails' + export SKU_NAME=aclgen2arm64TL ARCHITECTURE=ARM64 ENABLE_FIPS=false + MOCK_PRIMARY_PULL_FAIL=1 + EXPECTED_IMG_CUSTOMIZER_REF="$IMG_CUSTOMIZER_CONTAINER_FALLBACK" + When call check_artifact_name true + The status should be success + The line 1 of output should equal "https://download.invalid/cosi/${SKU_NAME}-${CAPTURED_SIG_VERSION}.cosi" + The line 2 of output should include "--blob ${SKU_NAME}-${CAPTURED_SIG_VERSION}.cosi" + End + + It 'stops when the primary pull fails and fallback is disabled' + export SKU_NAME=aclgen2arm64TL + MOCK_PRIMARY_PULL_FAIL=1 + When call check_artifact_name false + The status should be failure + The output should be blank + End + Describe 'variants with a shared capture version' Parameters aclgen2TL X86_64 false diff --git a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh index dfc98a02b36..4b3f0157a88 100755 --- a/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh +++ b/vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh @@ -41,6 +41,13 @@ generate_cosi_package_version() { printf '%s\n' "$version" } +validate_imagecustomizer_reference() { + if ! printf '%s\n' "$1" | grep -Eq '^[^[:space:]@]+@sha256:[0-9a-f]{64}$'; then + echo "##vso[task.logissue type=error]ImageCustomizer reference must be pinned with a sha256 digest" >&2 + return 1 + fi +} + # Source-guard: functions above are unit-tested; the flow below skips when sourced. ${__SOURCED__:+return} @@ -48,6 +55,7 @@ required_env_vars=( "DESTINATION_STORAGE_CONTAINER" "CAPTURED_SIG_VERSION" "SKU_NAME" + "IMAGE_VERSION" "IMG_CUSTOMIZER_CONTAINER" "AFD_DOWNLOAD_HOSTNAME" "COSI_CONTAINER" @@ -61,12 +69,12 @@ do fi done -# Optional GHCR fallback: when the MCR ImageCustomizer image -# (IMG_CUSTOMIZER_CONTAINER, including its tag) is unavailable, optionally fall -# back to pulling the published GitHub Container Registry image -# (IMG_CUSTOMIZER_CONTAINER_FALLBACK, also including its tag). Gated by the -# first script argument and defaults to "false" so the fallback is opt-in. +# Both primary and opt-in fallback images must be pinned before privileged execution. ALLOW_GHCR_FALLBACK="${1:-false}" +validate_imagecustomizer_reference "$IMG_CUSTOMIZER_CONTAINER" +if [ "${ALLOW_GHCR_FALLBACK,,}" = "true" ]; then + validate_imagecustomizer_reference "${IMG_CUSTOMIZER_CONTAINER_FALLBACK:-}" +fi WORK_DIR="$(pwd)/cosi-convert" mkdir -p "$WORK_DIR/build" "$WORK_DIR/out" @@ -185,11 +193,6 @@ COSI_SHA256=$(sha256sum "$STAGED_COSI" | awk '{print $1}') COSI_SHA1=$(sha1sum "$STAGED_COSI" | awk '{print $1}') COSI_SIZE=$(stat -c%s "$STAGED_COSI") -if [ -z "${IMAGE_VERSION:-}" ]; then - IMAGE_VERSION=$(date +%Y%m.%d.0) - echo "IMAGE_VERSION was not set, defaulting to ${IMAGE_VERSION}" -fi - # Normalize only the Nebraska-facing version; IMAGE_VERSION itself is unchanged. if ! COSI_IMAGE_VERSION="$(generate_cosi_package_version "${IMAGE_VERSION}" "${ENABLE_FIPS:-false}")"; then echo "##vso[task.logissue type=error]Failed to derive a strict-SemVer COSI package version from IMAGE_VERSION='${IMAGE_VERSION}' (ENABLE_FIPS='${ENABLE_FIPS:-false}')" From 3feb088f359d0caa3ed6c41eda8e49584d21c941 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:05:54 -0400 Subject: [PATCH 59/60] Skip unused conversion --- .pipelines/.vsts-vhd-builder-release.yaml | 4 ++-- .pipelines/templates/.builder-release-template.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index 2a8723310c5..63334bbe4e8 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -985,7 +985,7 @@ stages: # don't work under qemu emulation on the x86_64 build pool). These jobs run on # the native arm64 pool to convert the staged VHD and publish the COSI. - job: convertaclarm64tlgen2 - condition: and(succeeded(), eq('${{ parameters.buildaclarm64tlgen2 }}', true)) + condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), eq('${{ parameters.buildaclarm64tlgen2 }}', true)) dependsOn: buildaclarm64tlgen2 timeoutInMinutes: 120 pool: @@ -1016,7 +1016,7 @@ stages: parameters: artifactName: acl-arm64-tl-gen2 - job: convertaclarm64fipstlgen2 - condition: and(succeeded(), eq('${{ parameters.buildaclarm64fipstlgen2 }}', true)) + condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), eq('${{ parameters.buildaclarm64fipstlgen2 }}', true)) dependsOn: buildaclarm64fipstlgen2 timeoutInMinutes: 120 pool: diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index 50b3e275b71..ae44ae0133a 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -29,11 +29,11 @@ steps: tar --exclude='./.git' \ -czf "$(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz" . ls -lh "$(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz" - condition: and(succeeded(), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) + condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) displayName: Bundle source for arm64 COSI convert - task: PublishPipelineArtifact@1 - condition: and(succeeded(), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) + condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) displayName: Publish source bundle for arm64 COSI convert inputs: # artifactName is the per-job param (e.g. "acl-arm64-tl-gen2"); using it @@ -207,7 +207,7 @@ steps: echo "##vso[task.setvariable variable=CAPTURED_SIG_VERSION;isOutput=true]${CAPTURED_SIG_VERSION}" name: exportCapturedSigVersion displayName: Export Captured SIG Version for arm64 COSI convert - condition: and(succeeded(), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) + condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) - bash: | set -euo pipefail From fa2990f8ab98e88c150a67cb92f6c68b17008a04 Mon Sep 17 00:00:00 2001 From: Adams Anaglo <56308410+adamsanaglo@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:16:59 -0400 Subject: [PATCH 60/60] Gate COSI handoff --- .pipelines/.vsts-vhd-builder-release.yaml | 2 + .../templates/.builder-release-template.yaml | 98 +++++++++---------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-release.yaml b/.pipelines/.vsts-vhd-builder-release.yaml index 63334bbe4e8..9ea1224dfc0 100644 --- a/.pipelines/.vsts-vhd-builder-release.yaml +++ b/.pipelines/.vsts-vhd-builder-release.yaml @@ -923,6 +923,7 @@ stages: useOverrides: ${{ parameters.useOverrides }} overrideBranch: ${{ parameters.overrideBranch }} artifactName: acl-arm64-tl-gen2 + enableCosiPublishing: true - job: buildaclfipstlgen2 condition: eq('${{ parameters.buildaclfipstlgen2 }}', true) dependsOn: [ ] @@ -980,6 +981,7 @@ stages: useOverrides: ${{ parameters.useOverrides }} overrideBranch: ${{ parameters.overrideBranch }} artifactName: acl-arm64-fips-tl-gen2 + enableCosiPublishing: true # Native-arm64 COSI conversion jobs. The arm64 ACL build jobs above stage the # VHD in blob storage and skip COSI (ImageCustomizer's btrfs subvolume ioctls # don't work under qemu emulation on the x86_64 build pool). These jobs run on diff --git a/.pipelines/templates/.builder-release-template.yaml b/.pipelines/templates/.builder-release-template.yaml index ae44ae0133a..5248821744e 100644 --- a/.pipelines/templates/.builder-release-template.yaml +++ b/.pipelines/templates/.builder-release-template.yaml @@ -23,23 +23,22 @@ steps: # and it downloads this bundle instead). Must run AFTER the checkout above but # BEFORE Download CIS scanner (which drops binaries into vhdbuilder/ that # would otherwise bloat the tarball). - - bash: | - set -euo pipefail - cd "$(Build.SourcesDirectory)" - tar --exclude='./.git' \ - -czf "$(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz" . - ls -lh "$(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz" - condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) - displayName: Bundle source for arm64 COSI convert + - ${{ if parameters.enableCosiPublishing }}: + - bash: | + set -euo pipefail + cd "$(Build.SourcesDirectory)" + tar --exclude='./.git' \ + -czf "$(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz" . + ls -lh "$(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz" + condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) + displayName: Bundle source for arm64 COSI convert - - task: PublishPipelineArtifact@1 - condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) - displayName: Publish source bundle for arm64 COSI convert - inputs: - # artifactName is the per-job param (e.g. "acl-arm64-tl-gen2"); using it - # here guarantees a unique artifact name per build job at compile time. - artifactName: agentbaker-sources-${{ parameters.artifactName }} - targetPath: $(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz + - task: PublishPipelineArtifact@1 + condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) + displayName: Publish source bundle for arm64 COSI convert + inputs: + artifactName: agentbaker-sources-${{ parameters.artifactName }} + targetPath: $(Build.ArtifactStagingDirectory)/agentbaker-sources.tar.gz - task: UniversalPackages@0 displayName: Download CIS scanner @@ -192,22 +191,20 @@ steps: condition: always() displayName: Set Post-Build Variables - - bash: | - image_version="${IMAGE_VERSION:-$(date -u +%Y%m.%d.0)}" - echo "##vso[task.setvariable variable=IMAGE_VERSION]${image_version}" - echo "##vso[task.setvariable variable=IMAGE_VERSION;isOutput=true]${image_version}" - name: resolveCosiImageVersion - displayName: Resolve COSI Image Version - condition: and(succeeded(), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables.GENERATE_PUBLISHING_INFO, 'True')) - - # Expose the captured SIG version to the separate native-arm64 COSI convert job - # (arm64 ACL COSI conversion can't run under emulation, so it runs on the arm64 - # pool as its own job that consumes this output variable). - - bash: | - echo "##vso[task.setvariable variable=CAPTURED_SIG_VERSION;isOutput=true]${CAPTURED_SIG_VERSION}" - name: exportCapturedSigVersion - displayName: Export Captured SIG Version for arm64 COSI convert - condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) + - ${{ if parameters.enableCosiPublishing }}: + - bash: | + image_version="${IMAGE_VERSION:-$(date -u +%Y%m.%d.0)}" + echo "##vso[task.setvariable variable=IMAGE_VERSION]${image_version}" + echo "##vso[task.setvariable variable=IMAGE_VERSION;isOutput=true]${image_version}" + name: resolveCosiImageVersion + displayName: Resolve COSI Image Version + condition: and(succeeded(), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables.GENERATE_PUBLISHING_INFO, 'True')) + + - bash: | + echo "##vso[task.setvariable variable=CAPTURED_SIG_VERSION;isOutput=true]${CAPTURED_SIG_VERSION}" + name: exportCapturedSigVersion + displayName: Export Captured SIG Version for arm64 COSI convert + condition: and(succeeded(), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64')) - bash: | set -euo pipefail @@ -556,7 +553,7 @@ steps: succeeded(), eq(variables.ENVIRONMENT, 'tme'), eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - not(and(eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64'))), + not(and(eq('${{ parameters.enableCosiPublishing }}', true), eq(variables['OS_SKU'], 'AzureContainerLinux'), eq(variables['ARCHITECTURE'], 'ARM64'))), or( eq(variables.RUN_PREFETCH_OPTIMIZATION, 'False'), eq(variables.PREFETCH_COMPATIBLE, 'False') @@ -568,23 +565,24 @@ steps: # acquiring storage tokens, so copy the VHD to the immutable container and drop # the staging copy here (this x64 pool has storage auth), then hand the convert # job a short read-only SAS so it downloads the VHD with no storage login. - - task: AzureCLI@2 - inputs: - azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) - scriptType: bash - scriptLocation: inlineScript - inlineScript: | - bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh - name: exportVhdSas - condition: | - and( - succeeded(), - eq(variables.ENVIRONMENT, 'tme'), - eq(variables.GENERATE_PUBLISHING_INFO, 'True'), - eq(variables['OS_SKU'], 'AzureContainerLinux'), - eq(variables['ARCHITECTURE'], 'ARM64') - ) - displayName: Copy arm64 ACL VHD to Immutable + mint convert SAS + - ${{ if parameters.enableCosiPublishing }}: + - task: AzureCLI@2 + inputs: + azureSubscription: $(VHD_ARM_SERVICE_CONNECTION) + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + bash ./vhdbuilder/packer/imagecustomizer/scripts/export-vhd-sas.sh + name: exportVhdSas + condition: | + and( + succeeded(), + eq(variables.ENVIRONMENT, 'tme'), + eq(variables.GENERATE_PUBLISHING_INFO, 'True'), + eq(variables['OS_SKU'], 'AzureContainerLinux'), + eq(variables['ARCHITECTURE'], 'ARM64') + ) + displayName: Copy arm64 ACL VHD to Immutable + mint convert SAS - task: DownloadPipelineArtifact@2 displayName: Download Build Performance Program