diff --git a/.github/renovate.json b/.github/renovate.json index d08c9ad0686..de599c057a4 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -109,6 +109,7 @@ ], "matchPackageNames": [ "aks-secure-tls-bootstrap-client", + "az-oras", "azure-acr-credential-provider", "containernetworking-plugins", "containerd2", @@ -125,7 +126,8 @@ "node-exporter-kubernetes", "node-problem-detector-aks-config", "node-problem-detector-kubernetes", - "nvidia-device-plugin" + "nvidia-device-plugin", + "oras" ], "extractVersion": "^(?:\\d+:)?(?\\d+\\.\\d+\\.\\d+)(?:[-+].*)?$", "versioning": "semver" diff --git a/parts/common/components.json b/parts/common/components.json index 76c81bd8237..cf1d8b7bf8b 100644 --- a/parts/common/components.json +++ b/parts/common/components.json @@ -912,7 +912,49 @@ "downloadURL": "https://github.com/oras-project/oras/releases/download/v${version}/oras_${version}_linux_${CPU_ARCH}.tar.gz" } }, + "ubuntu": { + "r2604": { + "versionsV2": [ + { + "renovateTag": "name=az-oras, repository=production, os=ubuntu, release=26.04", + "latestVersion": "1.3.5" + } + ] + }, + "r2404": { + "versionsV2": [ + { + "renovateTag": "name=az-oras, repository=production, os=ubuntu, release=24.04", + "latestVersion": "1.3.5" + } + ] + }, + "r2204": { + "versionsV2": [ + { + "renovateTag": "name=az-oras, repository=production, os=ubuntu, release=22.04", + "latestVersion": "1.3.5" + } + ] + }, + "r2004": { + "versionsV2": [ + { + "renovateTag": "name=az-oras, repository=production, os=ubuntu, release=20.04", + "latestVersion": "1.3.5" + } + ] + } + }, "azurelinux": { + "v3.0": { + "versionsV2": [ + { + "renovateTag": "RPM_registry=https://packages.microsoft.com/azurelinux/3.0/prod/cloud-native/x86_64/repodata, name=oras, os=azurelinux, release=3.0", + "latestVersion": "1.3.5" + } + ] + }, "OSGUARD/v3.0": { "versionsV2": [ { @@ -922,15 +964,25 @@ ] } }, + "azurelinuxkata": { + "v3.0": { + "versionsV2": [ + { + "renovateTag": "RPM_registry=https://packages.microsoft.com/azurelinux/3.0/prod/cloud-native/x86_64/repodata, name=oras, os=azurelinux, release=3.0", + "latestVersion": "1.3.5" + } + ] + } + }, "windows": { "default": { "versionsV2": [ { - "renovateTag": "github-releases=oras-project/oras", - "latestVersion": "1.3.6" + "renovateTag": "", + "latestVersion": "1.3.6-1" } ], - "downloadURL": "https://github.com/oras-project/oras/releases/download/v${version}/oras_${version}_windows_amd64.zip" + "downloadURL": "https://packages.aks.azure.com/dalec-packages/oras/$($version.Split('-')[0])/windows/amd64/oras_${version}_amd64.zip" } } } diff --git a/parts/linux/cloud-init/artifacts/README-COMPONENTS.md b/parts/linux/cloud-init/artifacts/README-COMPONENTS.md index feb4f8fbd9f..e84b651738a 100644 --- a/parts/linux/cloud-init/artifacts/README-COMPONENTS.md +++ b/parts/linux/cloud-init/artifacts/README-COMPONENTS.md @@ -217,6 +217,8 @@ Trivy uses one `TRIVY_PMC_VERSION` in `trivy-scan.sh` for Ubuntu and Azure Linux Each resolution is logged as `requested -> full package version` and written to the VHD release notes. The release notes also include the distro's complete installed-package inventory; explicit resolution entries cover packages that are cached or unpacked without being registered as installed packages. +ORAS uses DALEC-built PMC packages on Ubuntu 20.04, 22.04, 24.04, and 26.04 and Azure Linux 3.0. The Ubuntu package is named `az-oras` (to avoid conflicting with Ubuntu's own `oras` package) but provides the `oras` executable; the VHD build extracts it to `/opt/bin/oras-` and symlinks `/opt/bin/oras`. Windows uses the DALEC ZIP, and Flatcar/ACL continue to use the upstream release archive. + ## Can I keep only 1 patch version? Yes. Just place the latest version of the component in `latestVersion`. `previousLatestVersion` is optional. diff --git a/spec/vhdbuilder/packer/test/linux_vhd_content_test_helpers_spec.sh b/spec/vhdbuilder/packer/test/linux_vhd_content_test_helpers_spec.sh index 8d0d025b5cf..979d7ce5364 100644 --- a/spec/vhdbuilder/packer/test/linux_vhd_content_test_helpers_spec.sh +++ b/spec/vhdbuilder/packer/test/linux_vhd_content_test_helpers_spec.sh @@ -305,4 +305,59 @@ Describe 'testCronPermissions invocation' The output should include "checkPathDoesNotExist:/etc/cron.deny" The output should not include "testCronPermissions: Skipping cron file check" End + + Describe 'testOrasBinaryInstalled helper function' + BeforeAll "eval \"\$(sed -n '/^testOrasBinaryInstalled()/,/^}/p' './vhdbuilder/packer/test/linux-vhd-content-test.sh')\"" + + err() { + printf '%s:%s\n' "$1" "$2" >&2 + } + + create_fake_oras() { + local path="$1" + local version="$2" + # shellcheck disable=SC2016 # $1 must stay literal in the generated script + printf '#!/bin/bash\nif [ "$1" = "version" ]; then\n echo "Version: %s"\n exit 0\nfi\nexit 1\n' "$version" > "$path" + chmod +x "$path" + } + + It 'passes when oras is installed with the expected version' + root_dir="${SHELLSPEC_WORKDIR}/oras-ok" + mkdir -p "$root_dir" + create_fake_oras "$root_dir/oras" "1.3.5" + + When call testOrasBinaryInstalled "1.3.5-2" "$root_dir/oras" + The status should equal 0 + The error should equal "" + End + + It 'passes when DALEC oras reports build metadata in its version' + root_dir="${SHELLSPEC_WORKDIR}/oras-dalec-build-metadata" + mkdir -p "$root_dir" + create_fake_oras "$root_dir/oras" "1.3.5+ubuntu.24.04" + + When call testOrasBinaryInstalled "1.3.5" "$root_dir/oras" + The status should equal 0 + The error should equal "" + End + + It 'fails when oras is missing' + root_dir="${SHELLSPEC_WORKDIR}/oras-missing" + mkdir -p "$root_dir" + + When call testOrasBinaryInstalled "1.3.5-2" "$root_dir/oras" + The status should equal 1 + The error should include "is missing or not executable" + End + + It 'fails when oras reports an unexpected upstream version' + root_dir="${SHELLSPEC_WORKDIR}/oras-version-mismatch" + mkdir -p "$root_dir" + create_fake_oras "$root_dir/oras" "1.3.4" + + When call testOrasBinaryInstalled "1.3.5-2" "$root_dir/oras" + The status should equal 1 + The error should include "oras version mismatch" + End + End End diff --git a/vhdbuilder/packer/install-dependencies.sh b/vhdbuilder/packer/install-dependencies.sh index 28a231a6436..79f6687bb60 100644 --- a/vhdbuilder/packer/install-dependencies.sh +++ b/vhdbuilder/packer/install-dependencies.sh @@ -782,8 +782,18 @@ cachePackageAndBinaryComponents() { ;; "oras") for version in ${PACKAGE_VERSIONS[@]}; do - evaluatedURL=$(evalPackageDownloadURL ${PACKAGE_DOWNLOAD_URL}) - installOras "${downloadDir}" "${evaluatedURL}" "${version}" + # OS entries without a downloadURL install the DALEC-built PMC package + if [ -z "${PACKAGE_DOWNLOAD_URL}" ] && isUbuntu; then + # The Ubuntu PMC package is named az-oras but ships /usr/bin/oras + downloadPkgFromVersion "az-oras" "${version}" "/opt/${name}/downloads" || exit $ERR_ORAS_DOWNLOAD_ERROR + installPackageFromCache "${name}" "${version}" "/opt/bin/${name}" || exit $ERR_ORAS_DOWNLOAD_ERROR + elif [ -z "${PACKAGE_DOWNLOAD_URL}" ] && isMarinerOrAzureLinux; then + downloadPkgFromVersion "${name}" "${version}" "$(getPackageDownloadDir "${name}")" || exit $ERR_ORAS_DOWNLOAD_ERROR + installRPMPackageFromFile "${name}" "${version}" "/opt/bin/${name}" || exit $ERR_ORAS_DOWNLOAD_ERROR + else + evaluatedURL=$(evalPackageDownloadURL ${PACKAGE_DOWNLOAD_URL}) + installOras "${downloadDir}" "${evaluatedURL}" "${version}" + fi echo " - oras version ${version}" >> ${VHD_LOGS_FILEPATH} done ;; diff --git a/vhdbuilder/packer/test/linux-vhd-content-test.sh b/vhdbuilder/packer/test/linux-vhd-content-test.sh index be3932f7019..d9864128fec 100644 --- a/vhdbuilder/packer/test/linux-vhd-content-test.sh +++ b/vhdbuilder/packer/test/linux-vhd-content-test.sh @@ -233,6 +233,34 @@ testAcrCredentialProviderInstalled() { echo "$test:Finish" } +testOrasBinaryInstalled() { + local expectedVersion="$1" + local orasPath="${2:-/opt/bin/oras}" + local test="testOrasBinaryInstalled" + local versionNoEpoch upstreamVersion installedVersion + + versionNoEpoch="${expectedVersion#*:}" + upstreamVersion="${versionNoEpoch%%-*}" + + if [ ! -x "${orasPath}" ]; then + err "$test" "expected executable ${orasPath} is missing or not executable" + return 1 + fi + + installedVersion=$("${orasPath}" version 2>/dev/null | awk -F': *' '/^Version:/ {print $2; exit}') + if [ -z "${installedVersion}" ]; then + err "$test" "unable to parse oras version from ${orasPath} version output" + return 1 + fi + # DALEC builds append build metadata, e.g. "1.3.5+ubuntu.24.04" + if [ "${installedVersion%%+*}" != "${upstreamVersion}" ]; then + err "$test" "oras version mismatch, expected ${upstreamVersion} but found ${installedVersion}" + return 1 + fi + + return 0 +} + testPackagesInstalled() { local test="testPackagesInstalled" CPU_ARCH=$(getCPUArch) # "arm64" or "amd64" @@ -322,6 +350,9 @@ testPackagesInstalled() { esac testContainerd "$version" "$pkgName" ;; + "oras") + testOrasBinaryInstalled "$version" + ;; esac break diff --git a/vhdbuilder/packer/test/windows-files-check.ps1 b/vhdbuilder/packer/test/windows-files-check.ps1 index c0e7ac61e55..94d5d4cce86 100644 --- a/vhdbuilder/packer/test/windows-files-check.ps1 +++ b/vhdbuilder/packer/test/windows-files-check.ps1 @@ -21,7 +21,6 @@ $SkipMapForSignature = @{ "aks-windows-cse-scripts-v0.0.32.zip" = @(); "azure-vnet-cni-windows-amd64-v1.6.21.zip" = @(); "azure-vnet-cni-windows-amd64-v1.5.38.zip" = @(); - "oras_1.3.6_windows_amd64.zip" = @(); } # $SkipSignatureCheckForBinaries is defined in windows-vhd-configuration.ps1 (dot-sourced above) diff --git a/vhdbuilder/packer/windows/components_json_helpers.ps1 b/vhdbuilder/packer/windows/components_json_helpers.ps1 index e3844df7021..daddd0acb5f 100644 --- a/vhdbuilder/packer/windows/components_json_helpers.ps1 +++ b/vhdbuilder/packer/windows/components_json_helpers.ps1 @@ -304,6 +304,44 @@ function GetWindowsPackageVersionFromComponentsJson { throw "Could not find package '$packageName' in components.json" } +function GetWindowsPackageDownloadUrlFromComponentsJson { + Param( + [Parameter(Mandatory = $true)][Object] + $componentsJsonContent, + + [Parameter(Mandatory = $true)][String] + $packageName + ) + + foreach ($package in $componentsJsonContent.Packages) { + if ($package.name -ne $packageName) { + continue + } + + $part = GetWindowsDownloadPartForPackage $package + if ($null -eq $part -or $null -eq $part.versionsV2 -or $part.versionsV2.Count -eq 0) { + throw "Could not find Windows versions for package '$packageName' in components.json" + } + + $downloadUrl = $part.windowsDownloadUrl + if ($null -eq $downloadUrl -or $downloadUrl -eq "") { + $downloadUrl = $part.downloadUrl + } + if ($null -eq $downloadUrl -or $downloadUrl -eq "") { + throw "Could not find a valid Windows download URL for package '$packageName' in components.json" + } + + $latestVersion = $part.versionsV2[0].latestVersion + if ([string]::IsNullOrEmpty($latestVersion) -or $latestVersion -eq "") { + throw "Could not find a valid Windows version for package '$packageName' in components.json" + } + + return ReplaceVarsInUrl -versionString $latestVersion -stringToReplace $downloadUrl + } + + throw "Could not find package '$packageName' in components.json" +} + function GetOCIArtifactsFromComponentsJson { Param( diff --git a/vhdbuilder/packer/windows/components_json_helpers.tests.ps1 b/vhdbuilder/packer/windows/components_json_helpers.tests.ps1 index ebc02a52fa1..594b1f4de06 100644 --- a/vhdbuilder/packer/windows/components_json_helpers.tests.ps1 +++ b/vhdbuilder/packer/windows/components_json_helpers.tests.ps1 @@ -821,6 +821,20 @@ Describe 'Gets the Binaries' { $version | Should -Be "1.3.3" } + It 'can resolve Windows package download URL by name' { + $componentsJson.Packages[0] | Add-Member -NotePropertyName "name" -NotePropertyValue "oras" -Force + $componentsJson.Packages[0].downloadUris.windows.default.versionsV2 = @( + [PSCustomObject]@{ + latestVersion = "1.3.5-2" + } + ) + $componentsJson.Packages[0].downloadUris.windows.default.downloadURL = 'https://packages.aks.azure.com/dalec-packages/oras/${versionNoBuild}/windows/amd64/oras_${version}_amd64.zip' + + $url = GetWindowsPackageDownloadUrlFromComponentsJson $componentsJson "oras" + + $url | Should -Be 'https://packages.aks.azure.com/dalec-packages/oras/1.3.5/windows/amd64/oras_1.3.5-2_amd64.zip' + } + It 'given there are two packages in the same directory, it combines them' { $testString = '{ "Packages": [ diff --git a/vhdbuilder/packer/windows/configure-windows-vhd.ps1 b/vhdbuilder/packer/windows/configure-windows-vhd.ps1 index 3f3725df36c..8e5b8d9d150 100644 --- a/vhdbuilder/packer/windows/configure-windows-vhd.ps1 +++ b/vhdbuilder/packer/windows/configure-windows-vhd.ps1 @@ -245,8 +245,15 @@ function Pull-OCIArtifact throw "Unable to resolve ORAS version from components.json" } - $orasZip = "oras_${orasVersion}_windows_amd64.zip" - $orasUrl = "https://github.com/oras-project/oras/releases/download/v${orasVersion}/${orasZip}" + $orasUrl = $global:orasDownloadUrl + if ([string]::IsNullOrEmpty($orasUrl)) { + throw "Unable to resolve ORAS download URL from components.json" + } + + $orasZip = [System.IO.Path]::GetFileName($orasUrl.Split('?')[0]) + if ([string]::IsNullOrEmpty($orasZip)) { + throw "Unable to resolve ORAS archive name from download URL '$orasUrl'" + } Write-Log "Downloading oras v${orasVersion}" Invoke-WebRequest -UseBasicParsing $orasUrl -OutFile "$global:aksTempDir\$orasZip" diff --git a/vhdbuilder/packer/windows/windows-vhd-configuration.ps1 b/vhdbuilder/packer/windows/windows-vhd-configuration.ps1 index 311fbd2fa48..ab71d3ffc51 100644 --- a/vhdbuilder/packer/windows/windows-vhd-configuration.ps1 +++ b/vhdbuilder/packer/windows/windows-vhd-configuration.ps1 @@ -87,6 +87,7 @@ if (-not ($validSKU -contains $windowsSKU)) # versions synced. $global:defaultContainerdPackageUrl = GetDefaultContainerDFromComponentsJson $componentsJson $global:orasVersion = GetWindowsPackageVersionFromComponentsJson $componentsJson "oras" +$global:orasDownloadUrl = GetWindowsPackageDownloadUrlFromComponentsJson $componentsJson "oras" # defenderUpdateUrl refers to the latest windows defender platform update $global:defenderUpdateUrl = GetDefenderUpdateUrl $windowsSettingsJson