feat: download kubelet with oras in network isolated windows cluster#8042
feat: download kubelet with oras in network isolated windows cluster#8042jiashun0011 wants to merge 1 commit intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds support for downloading Windows kubelet binaries via ORAS when running in a network-isolated cluster context (using the bootstrap profile container registry), and extends test coverage to exercise the new path.
Changes:
- Update
Get-KubePackageto download kubelet binaries via ORAS whenBootstrapProfileContainerRegistryServeris set; otherwise keep HTTP download behavior. - Add/extend Pester tests for ORAS vs HTTP selection behavior in
Get-KubePackage. - Add a new Windows network-isolated e2e scenario intended to validate ORAS-based kubelet download behavior.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| staging/cse/windows/kubeletfunc.ps1 | Adds ORAS-based kubelet package download branch when bootstrap profile registry is configured. |
| staging/cse/windows/kubeletfunc.tests.ps1 | Adds Pester contexts to cover ORAS vs HTTP download paths for kubelet package retrieval. |
| e2e/scenario_win_test.go | Adds a network-isolated Windows e2e test and alters Windows 2025 bootstrap mutator to use a custom CSE package URL. |
You can also share your feedback on Copilot code review. Take the survey.
| # The ORAS invocation line in Get-KubePackage evaluates $global:OrasPath | ||
| # and then invokes 'pull' as a separate command with the remaining arguments. | ||
| # Define and mock 'pull' so the invocation succeeds silently in tests. | ||
| function pull { } | ||
| Mock pull -MockWith {} |
There was a problem hiding this comment.
These tests are currently structured around the broken ORAS invocation semantics (mocking a pull function so $global:OrasPath pull ... doesn't error). That means the test suite would still pass even if the script never invokes oras.exe. After fixing Get-KubePackage to correctly run ORAS (e.g., via & $global:OrasPath pull ...), update the tests to validate the ORAS invocation through a mockable wrapper function (e.g., Invoke-OrasPull) instead of mocking pull.
|
forget to delete? |
staging/cse/windows/kubeletfunc.ps1
Outdated
| # download kubelet binraries based on whether BootstrapProfileContainerRegistryServer is set. | ||
| if ($global:BootstrapProfileContainerRegistryServer) { | ||
| Logs-To-Event -TaskName "AKS.WindowsCSE.DownloadKubletBinariesWithOras" -TaskMessage "Start to download kubelet binaries with oras. KubeBinariesVersion: $global:KubeBinariesVersion, BootstrapProfileContainerRegistryServer: $global:BootstrapProfileContainerRegistryServer" | ||
| $global:OrasPath pull $global:BootstrapProfileContainerRegistryServer/aks/packages/kubernetes/windowszip:$global:KubeBinariesVersion --platform="windows/amd64" --registry-config=$global:OrasRegistryConfigFile --output $zipfile |
staging/cse/windows/kubeletfunc.ps1
Outdated
| # download kubelet binraries based on whether BootstrapProfileContainerRegistryServer is set. | ||
| if ($global:BootstrapProfileContainerRegistryServer) { | ||
| Logs-To-Event -TaskName "AKS.WindowsCSE.DownloadKubletBinariesWithOras" -TaskMessage "Start to download kubelet binaries with oras. KubeBinariesVersion: $global:KubeBinariesVersion, BootstrapProfileContainerRegistryServer: $global:BootstrapProfileContainerRegistryServer" | ||
| $global:OrasPath pull $global:BootstrapProfileContainerRegistryServer/aks/packages/kubernetes/windowszip:$global:KubeBinariesVersion --platform="windows/amd64" --registry-config=$global:OrasRegistryConfigFile --output $zipfile |
There was a problem hiding this comment.
is it possible that windowszip version is different from k8sversion?
for example, k8s version is v1.30.100, package version might be v1.30.100-lts (with suffix)
76afb46 to
4450dfb
Compare
4450dfb to
bac8e29
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
You can also share your feedback on Copilot code review. Take the survey.
| Mock Set-ExitCode -MockWith { throw "Set-ExitCode:$($ExitCode):$ErrorMessage" } | ||
| Mock Get-Item -MockWith { return New-Object -TypeName PSObject -Property @{ FullName = $DestinationPath } } |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
| { DownloadFileWithOras -Reference $reference -DestinationPath $destPath -ExitCode 80 } | Should -Not -Throw | ||
|
|
||
| Assert-MockCalled -CommandName 'Write-Log' -ParameterFilter { | ||
| $message -like "*platform=windows/amd64*" | ||
| } |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
| Set-ExitCode -ExitCode $global:WINDOWS_CSE_ERROR_ORAS_PULL_WINDOWSZIP_FAIL -ErrorMessage "DownloadFileWithOras function is not available. networkisolatedclusterfunc.ps1 may not be sourced." | ||
| } | ||
| Logs-To-Event -TaskName "AKS.WindowsCSE.DownloadKubletBinariesWithOras" -TaskMessage "Start to download kubelet binaries with oras. KubeBinariesVersion: $global:KubeBinariesVersion, BootstrapProfileContainerRegistryServer: $global:BootstrapProfileContainerRegistryServer" | ||
| $orasReference = "$($global:BootstrapProfileContainerRegistryServer)/aks/packages/kubernetes/windowszip:v$($global:KubeBinariesVersion)" |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
| for ($i = 0; $i -le $maxRetries; $i++) { | ||
| try { | ||
| DownloadFileWithOras -Reference $orasReference -DestinationPath $zipfile -ExitCode $global:WINDOWS_CSE_ERROR_ORAS_PULL_WINDOWSZIP_FAIL | ||
| break | ||
| } catch { | ||
| Write-Log "Attempt ${i}: oras pull failed for $orasReference. Error: $_" | ||
| if ($i -eq $maxRetries) { | ||
| Set-ExitCode -ExitCode $global:WINDOWS_CSE_ERROR_ORAS_PULL_WINDOWSZIP_FAIL -ErrorMessage "Exhausted retries for oras pull $orasReference. Error: $_" | ||
| } |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
You can also share your feedback on Copilot code review. Take the survey.
| $downloadTimer = [System.Diagnostics.Stopwatch]::StartNew() | ||
| $orasArgs = @( | ||
| "pull", | ||
| $Reference, | ||
| "--platform=$Platform", | ||
| "--registry-config=$($global:OrasRegistryConfigFile)", | ||
| "--output", $tempDir | ||
| ) | ||
| & $global:OrasPath @orasArgs | ||
| if ($LASTEXITCODE -ne 0) { | ||
| $downloadTimer.Stop() | ||
| Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue | ||
| Set-ExitCode -ExitCode $ExitCode -ErrorMessage "oras pull failed with exit code $LASTEXITCODE for $Reference" | ||
| } | ||
| $downloadTimer.Stop() | ||
| $elapsedMs = $downloadTimer.ElapsedMilliseconds | ||
|
|
||
| # Find the downloaded file in the temp directory and move it to the desired path | ||
| $downloadedFile = Get-ChildItem -Path $tempDir -File | Select-Object -First 1 | ||
| if (-not $downloadedFile) { | ||
| Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue | ||
| Set-ExitCode -ExitCode $ExitCode -ErrorMessage "oras pull succeeded but no file found in temp directory for $Reference" | ||
| } | ||
|
|
||
| Write-Log "Downloaded file name: $($downloadedFile.Name) (size: $($downloadedFile.Length) bytes) in temp directory $tempDir" | ||
|
|
||
| # Ensure the destination parent directory exists | ||
| $destDir = [System.IO.Path]::GetDirectoryName($DestinationPath) | ||
| if ($destDir -and -not (Test-Path $destDir)) { | ||
| New-Item -ItemType Directory -Path $destDir -Force | Out-Null | ||
| } | ||
|
|
||
| # Remove existing destination if present, then move the downloaded file | ||
| if (Test-Path $DestinationPath) { | ||
| Remove-Item -Path $DestinationPath -Force | ||
| } | ||
| Write-Log "Moving $($downloadedFile.FullName) to $DestinationPath" | ||
| Move-Item -Path $downloadedFile.FullName -Destination $DestinationPath -Force | ||
| Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue | ||
|
|
||
| if ($global:AppInsightsClient -ne $null) { | ||
| $event = New-Object "Microsoft.ApplicationInsights.DataContracts.EventTelemetry" | ||
| $event.Name = "FileDownload" | ||
| $event.Properties["FileName"] = $Reference | ||
| $event.Properties["Method"] = "oras" | ||
| $event.Metrics["DurationMs"] = $elapsedMs | ||
| $global:AppInsightsClient.TrackEvent($event) | ||
| } | ||
|
|
||
| Write-Log "Downloaded $Reference to $DestinationPath via oras in $elapsedMs ms" | ||
| Get-Item $DestinationPath -ErrorAction Continue | Format-List | Out-String | Write-Log |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
| nbc.ContainerService.Properties.SecurityProfile = &datamodel.SecurityProfile{ | ||
| PrivateEgress: &datamodel.PrivateEgress{ | ||
| Enabled: true, | ||
| ContainerRegistryServer: fmt.Sprintf("%s.azurecr.io/aks-managed-repository", config.PrivateACRName(config.Config.DefaultLocation)), |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
| It "Should call DownloadFileWithOras with correct reference when BootstrapProfileContainerRegistryServer is set" { | ||
| Get-KubePackage -KubeBinariesSASURL 'https://xxx.blob.core.windows.net/kubernetes/v1.29.2/windowszip/v1.29.2-1int.zip' | ||
| Assert-MockCalled -CommandName 'DownloadFileWithOras' -Exactly -Times 1 -ParameterFilter { | ||
| $Reference -eq 'myregistry.azurecr.io/aks/packages/kubernetes/windowszip:v1.29.2' -and | ||
| $DestinationPath -eq 'c:\k.zip' -and | ||
| $ExitCode -eq $global:WINDOWS_CSE_ERROR_ORAS_PULL_WINDOWSZIP_FAIL | ||
| } |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
| } | ||
| Logs-To-Event -TaskName "AKS.WindowsCSE.DownloadKubletBinariesWithOras" -TaskMessage "Start to download kubelet binaries with oras. KubeBinariesVersion: $global:KubeBinariesVersion, BootstrapProfileContainerRegistryServer: $global:BootstrapProfileContainerRegistryServer" | ||
| $orasReference = "$($global:BootstrapProfileContainerRegistryServer)/aks/packages/kubernetes/windowszip:v$($global:KubeBinariesVersion)" | ||
| $maxRetries = 5 |
There was a problem hiding this comment.
can we share same retry with DownloadFileOverHttp to minimize the code change?
There was a problem hiding this comment.
for example put if bootstrapProfileRegistry at line 225
There was a problem hiding this comment.
I feel keeping them separate makes the logic more straightforward.
Although both paths implement retries, the actual download mechanisms are different (oras vs HTTP), and the error handling is slightly different as well. Combining them into a shared retry loop may make the flow harder to read.
Given this code runs in CSE during node bootstrap, I’d prefer keeping it explicit and easy to follow.
There was a problem hiding this comment.
Per discussion, I agree keeping the PR change smaller, especially avoiding too much change in existing logic function structure, is a good point.
I’m okay with this direction.
Using two separate if blocks also seems fine to me. It keeps the original HTTP path mostly untouched, while isolating the new oras path into its own helper, so the diff is smaller and easier to review. I also agree not using else gives a bit more flexibility if we need to extend the condition handling later.
I’ll update it in this way.
deleted |
87651bd to
bf626ee
Compare
bf626ee to
663af34
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
You can also share your feedback on Copilot code review. Take the survey.
| try { | ||
| DownloadFileWithOras -Reference $orasReference -DestinationPath $zipfile -ExitCode $global:WINDOWS_CSE_ERROR_ORAS_PULL_WINDOWSZIP_FAIL | ||
| break | ||
| } catch { | ||
| Write-Log "Attempt ${i}: oras pull failed for $orasReference. Error: $_" | ||
| if ($i -eq $maxRetries) { | ||
| Set-ExitCode -ExitCode $global:WINDOWS_CSE_ERROR_ORAS_PULL_WINDOWSZIP_FAIL -ErrorMessage "Exhausted retries for oras pull $orasReference. Error: $_" | ||
| } | ||
| Start-Sleep -Seconds $retryDelaySeconds |
| $downloadTimer = [System.Diagnostics.Stopwatch]::StartNew() | ||
| $orasArgs = @( | ||
| "pull", | ||
| $Reference, | ||
| "--platform=$Platform", | ||
| "--registry-config=$($global:OrasRegistryConfigFile)", | ||
| "--output", $tempDir | ||
| ) | ||
| & $global:OrasPath @orasArgs | ||
| if ($LASTEXITCODE -ne 0) { | ||
| $downloadTimer.Stop() | ||
| Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue | ||
| Set-ExitCode -ExitCode $ExitCode -ErrorMessage "oras pull failed with exit code $LASTEXITCODE for $Reference" | ||
| } | ||
| $downloadTimer.Stop() | ||
| $elapsedMs = $downloadTimer.ElapsedMilliseconds | ||
|
|
||
| # Find the downloaded file in the temp directory and move it to the desired path | ||
| $downloadedFile = Get-ChildItem -Path $tempDir -File | Select-Object -First 1 | ||
| if (-not $downloadedFile) { | ||
| Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue | ||
| Set-ExitCode -ExitCode $ExitCode -ErrorMessage "oras pull succeeded but no file found in temp directory for $Reference" | ||
| } | ||
|
|
||
| Write-Log "Downloaded file name: $($downloadedFile.Name) (size: $($downloadedFile.Length) bytes) in temp directory $tempDir" | ||
|
|
||
| # Ensure the destination parent directory exists | ||
| $destDir = [System.IO.Path]::GetDirectoryName($DestinationPath) | ||
| if ($destDir -and -not (Test-Path $destDir)) { | ||
| New-Item -ItemType Directory -Path $destDir -Force | Out-Null | ||
| } | ||
|
|
||
| # Remove existing destination if present, then move the downloaded file | ||
| if (Test-Path $DestinationPath) { | ||
| Remove-Item -Path $DestinationPath -Force | ||
| } | ||
| Write-Log "Moving $($downloadedFile.FullName) to $DestinationPath" | ||
| Move-Item -Path $downloadedFile.FullName -Destination $DestinationPath -Force | ||
| Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue |
| { DownloadFileWithOras -Reference $reference -DestinationPath $destPath -ExitCode 80 } | Should -Not -Throw | ||
|
|
||
| Assert-MockCalled -CommandName 'Write-Log' -ParameterFilter { | ||
| $message -like "*platform=windows/amd64*" | ||
| } |
| It "should call oras with correct arguments on success" { | ||
| $reference = "myregistry.azurecr.io/aks/packages/kubernetes/windowszip:1.29.2" | ||
| $destPath = "c:\k.zip" | ||
|
|
||
| $global:OrasPath = "Write-Output" | ||
| { DownloadFileWithOras -Reference $reference -DestinationPath $destPath -ExitCode 80 } | Should -Not -Throw | ||
| } |
663af34 to
411dd66
Compare
|
There was an error handling pipeline event fa0590be-b5f0-479e-812d-33890e8827ec. |
411dd66 to
af24e0a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
You can also share your feedback on Copilot code review. Take the survey.
| $maxRetries = 5 | ||
| $retryDelaySeconds = 10 | ||
| for ($i = 0; $i -le $maxRetries; $i++) { | ||
| try { | ||
| DownloadFileWithOras -Reference $orasReference -DestinationPath $zipfile -ExitCode $global:WINDOWS_CSE_ERROR_ORAS_PULL_WINDOWSZIP_FAIL | ||
| break | ||
| } catch { | ||
| Write-Log "Attempt ${i}: oras pull failed for $orasReference. Error: $_" | ||
| if ($i -eq $maxRetries) { | ||
| Set-ExitCode -ExitCode $global:WINDOWS_CSE_ERROR_ORAS_PULL_WINDOWSZIP_FAIL -ErrorMessage "Exhausted retries for oras pull $orasReference. Error: $_" | ||
| } | ||
| Start-Sleep -Seconds $retryDelaySeconds | ||
| } | ||
| } |
| if ($?) { | ||
| break | ||
|
|
||
| # download kubelet binraries via http if BootstrapProfileContainerRegistryServer is not set |
| Tags: Tags{ | ||
| NetworkIsolated: true, | ||
| NonAnonymousACR: false, | ||
| }, |
| } | ||
| } | ||
|
|
||
| <<<<<<< HEAD |
| BeforeEach { | ||
| $global:OrasPath = "C:\aks-tools\oras\oras.exe" | ||
| $global:OrasRegistryConfigFile = "C:\oras-config.json" | ||
| $global:AppInsightsClient = $null |
What this PR does / why we need it:
/kind feature
Which issue(s) this PR fixes:
Download kubelet with oras in network isolated windows cluster.