Skip to content
46 changes: 39 additions & 7 deletions staging/cse/windows/configfunc.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,8 @@ function Install-CredentialProvider {
[Parameter(Mandatory = $false)][string]
$CustomCloudContainerRegistryDNSSuffix
)

try {
# Out of tree credential provider is turned on as a must after 1.30, and is optinal in 1.29, for cluster < 1.29, it's not enabled.
# Out of tree credential provider is turned on as a must after 1.30, and is optional in 1.29, for cluster < 1.29, it's not enabled.
# And only when it's enabled, the credential provider flags are set.
$global:credentialProviderConfigPath = ""
$global:credentialProviderBinDir = ""
Expand All @@ -454,12 +453,45 @@ function Install-CredentialProvider {

Write-Log "Download credential provider binary from $global:CredentialProviderURL to $global:credentialProviderBinDir"
Comment thread
fseldow marked this conversation as resolved.
$tempDir = New-TemporaryDirectory
$credentialproviderbinaryPackage = "$tempDir\credentialprovider.tar.gz"
DownloadFileOverHttp -Url $global:CredentialProviderURL -DestinationPath $credentialproviderbinaryPackage -ExitCode $global:WINDOWS_CSE_ERROR_DOWNLOAD_CREDEDNTIAL_PROVIDER
tar -xzf $credentialproviderbinaryPackage -C $tempDir
if ($LASTEXITCODE -ne 0) {
throw "Failed to extract the '$credentialproviderbinaryPackage' archive."
if ([string]::IsNullOrEmpty($global:BootstrapProfileContainerRegistryServer)) {
Comment thread
fseldow marked this conversation as resolved.
$credentialproviderbinaryPackage = "$tempDir\credentialprovider.tar.gz"
Comment thread
fseldow marked this conversation as resolved.
DownloadFileOverHttp -Url $global:CredentialProviderURL -DestinationPath $credentialproviderbinaryPackage -ExitCode $global:WINDOWS_CSE_ERROR_DOWNLOAD_CREDEDNTIAL_PROVIDER
tar -xzf $credentialproviderbinaryPackage -C $tempDir
if ($LASTEXITCODE -ne 0) {
throw "Failed to extract the '$credentialproviderbinaryPackage' archive."
Comment thread
fseldow marked this conversation as resolved.
}
} else {
# network isolated cluster
# download azure acr credential provider binaries via oras if BootstrapProfileContainerRegistryServer is set
if (-not (Get-Command 'DownloadFileWithOras' -ErrorAction SilentlyContinue)) {
Set-ExitCode -ExitCode $global:WINDOWS_CSE_ERROR_ORAS_PULL_CREDENTIAL_PROVIDER -ErrorMessage "DownloadFileWithOras function is not available. networkisolatedclusterfunc.ps1 may not be sourced."
}
$credentialproviderbinaryPackage = "$tempDir\credentialprovider.zip"
# Handle both URL styles:
# 1) .../azure-acr-credential-provider/1.34.0/windows/... for dalec url, e.g. https://packages.aks.azure.com/dalec-packages/azure-acr-credential-provider/1.34.0/windows/amd64/azure-acr-credential-provider_1.34.0-1_amd64.zip
# 2) .../cloud-provider-azure/v1.34.0/binaries/... for legacy binaries url, e.g. https://packages.aks.azure.com/cloud-provider-azure/v1.34.0/binaries/azure-acr-credential-provider-linux-amd64-v1.34.0.tar.gz
Comment thread
fseldow marked this conversation as resolved.
# If version is missing in URL, fall back to KubeBinariesVersion.
$packageVersion = ([regex]::Match($global:CredentialProviderURL, '/v?(\d+(?:\.\d+)+)(?=/)')).Groups[1].Value
if ([string]::IsNullOrEmpty($packageVersion)) {
$packageVersion = ([regex]::Match($global:CredentialProviderURL, '(?:^|[._-])v?(\d+(?:\.\d+)+)(?:$|[._-])')).Groups[1].Value
}
if ([string]::IsNullOrEmpty($packageVersion)) {
Write-Warning "Unexpected CredentialProviderURL format, version is not found in URL. CredentialProviderURL: $global:CredentialProviderURL. Fall back to KubeBinariesVersion: $global:KubeBinariesVersion"
$packageVersion = $global:KubeBinariesVersion
$packageVersion = $packageVersion.TrimStart('v')
}
Logs-To-Event -TaskName "AKS.WindowsCSE.DownloadCredentialProviderBinariesWithOras" -TaskMessage "Start to download azure acr credential provider binaries with oras. KubeBinariesVersion: $global:KubeBinariesVersion, BootstrapProfileContainerRegistryServer: $global:BootstrapProfileContainerRegistryServer"
$orasReference = "$($global:BootstrapProfileContainerRegistryServer)/aks/packages/kubernetes/azure-acr-credential-provider:v$($packageVersion)"
Comment thread
fseldow marked this conversation as resolved.
try {
Retry-Command -Command "DownloadFileWithOras" -Args @{Reference = $orasReference; DestinationPath = $credentialproviderbinaryPackage } -Retries 5 -RetryDelaySeconds 10
}
Comment thread
fseldow marked this conversation as resolved.
catch {
del $tempDir -Recurse -Force -ErrorAction SilentlyContinue
Set-ExitCode -ExitCode $global:WINDOWS_CSE_ERROR_ORAS_PULL_CREDENTIAL_PROVIDER -ErrorMessage "Exhausted retries for oras pull $orasReference. Error: $_"
}
AKS-Expand-Archive -Path $credentialproviderbinaryPackage -DestinationPath $tempDir
}

Create-Directory -FullPath $global:credentialProviderBinDir
cp "$tempDir\azure-acr-credential-provider.exe" "$global:credentialProviderBinDir\acr-credential-provider.exe"
# acr-credential-provider.exe cannot be found by kubelet through provider name before the fix https://github.com/kubernetes/kubernetes/pull/120291
Expand Down
97 changes: 87 additions & 10 deletions staging/cse/windows/configfunc.tests.ps1
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
BeforeAll {
. $PSScriptRoot\..\..\..\parts\windows\windowscsehelper.ps1
. $PSScriptRoot\networkisolatedclusterfunc.ps1
. $PSCommandPath.Replace('.tests.ps1','.ps1')

$capturedContent = $null
Mock Set-Content -MockWith {
Mock Set-Content -MockWith {
param($Path, $Value)
$script:capturedContent = $Value
$script:capturedContent = $Value
} -Verifiable

Mock Remove-Item
Expand Down Expand Up @@ -67,7 +68,7 @@ Describe 'Resize-OSDrive' {
Write-Host "Invoke-Executable $Executable $ArgList $ExitCode"
} -Verifiable
}

Context 'success' {
It "Should call Invoke-Executable to Diskpart once" {
Mock Get-Disk -MockWith {
Expand Down Expand Up @@ -114,7 +115,7 @@ Describe 'Resize-OSDrive' {
}
}

Describe 'Config-CredentialProvider' {
Describe 'Config-CredentialProvider' {
BeforeEach {
$global:credentialProviderConfigDir = "staging\cse\windows\credentialProvider.tests.suites"
$CredentialProviderConfPATH=[Io.path]::Combine("$global:credentialProviderConfigDir", "credential-provider-config.yaml")
Expand All @@ -126,26 +127,26 @@ Describe 'Config-CredentialProvider' {

AfterEach {
Remove-Item -Path $CredentialProviderConfPATH
}
}

Context 'CustomCloudContainerRegistryDNSSuffix is empty' {
It "should match the expected config file content" {
$expectedCredentialProviderConfig = Read-Format-Yaml ([Io.path]::Combine($credentialProviderConfigDir, "CustomCloudContainerRegistryDNSSuffixEmpty.config.yaml"))
Config-CredentialProvider -KubeDir $credentialProviderConfigDir -CredentialProviderConfPath $CredentialProviderConfPATH -CustomCloudContainerRegistryDNSSuffix ""

$acutalCredentialProviderConfig = Read-Format-Yaml $CredentialProviderConfPATH
# Compare the content by normalizing whitespace and line endings
$normalizedExpected = $expectedCredentialProviderConfig.Trim().Replace("`r`n", "`n")
$normalizedActual = $acutalCredentialProviderConfig.Trim().Replace("`r`n", "`n")
Comment thread
fseldow marked this conversation as resolved.
$normalizedActual | Should -Be $normalizedExpected
}
}
}
Context 'CustomCloudContainerRegistryDNSSuffix is not empty' {
It "should match the expected config file content" {
$expectedCredentialProviderConfig = Read-Format-Yaml ([Io.path]::Combine($credentialProviderConfigDir, "CustomCloudContainerRegistryDNSSuffixNotEmpty.config.yaml"))
Config-CredentialProvider -KubeDir $credentialProviderConfigDir -CredentialProviderConfPath $CredentialProviderConfPATH -CustomCloudContainerRegistryDNSSuffix ".azurecr.microsoft.fakecloud"
$acutalCredentialProviderConfig = Read-Format-Yaml $CredentialProviderConfPATH

# Compare the content by normalizing whitespace and line endings
$normalizedExpected = $expectedCredentialProviderConfig.Trim().Replace("`r`n", "`n")
$normalizedActual = $acutalCredentialProviderConfig.Trim().Replace("`r`n", "`n")
Expand All @@ -170,7 +171,7 @@ Describe 'Validate-CredentialProviderConfigFlags' {
Write-Host "Set-ExitCode $ExitCode $ErrorMessage"
} -Verifiable
}

Context 'success' {
It "Should return expected config path and bin path" {
$expectedCredentialProviderConfigPath="c:\k\credential-provider-config.yaml"
Expand Down Expand Up @@ -211,4 +212,80 @@ Describe 'Validate-CredentialProviderConfigFlags' {
Assert-MockCalled -CommandName "Set-ExitCode" -Exactly -Times 1 -ParameterFilter { $ExitCode -eq $global:WINDOWS_CSE_ERROR_CREDENTIAL_PROVIDER_CONFIG }
}
}
}
}

Describe 'Install-CredentialProvider' {
BeforeEach {
$global:credentialProviderConfigPath = ""
$global:credentialProviderBinDir = ""
$global:KubeletConfigArgs = @(
"--image-credential-provider-config=c:\k\credential-provider-config.yaml",
"--image-credential-provider-bin-dir=c:\var\lib\kubelet\credential-provider"
)
$global:CredentialProviderURL = "https://packages.aks.azure.com/dalec-packages/azure-acr-credential-provider/1.34.0/windows/amd64/azure-acr-credential-provider_1.34.0-1_amd64.zip"
$global:BootstrapProfileContainerRegistryServer = "myregistry.azurecr.io"
$global:KubeBinariesVersion = "1.31.9"
$script:lastDownloadReference = ""

Mock Config-CredentialProvider
Mock New-TemporaryDirectory -MockWith { "C:\temp\credprovider" }
Mock DownloadFileOverHttp
Mock DownloadFileWithOras -MockWith {
param(
[string]$Reference,
[string]$DestinationPath,
[string]$Platform
)
$script:lastDownloadReference = $Reference
}
Comment thread
fseldow marked this conversation as resolved.
Mock AKS-Expand-Archive
Mock Create-Directory
Mock cp
Mock del
Mock tar -MockWith { $global:LASTEXITCODE = 0 }
Mock Get-Command -MockWith {
[pscustomobject]@{ Name = "DownloadFileWithOras" }
} -ParameterFilter { $Name -eq 'DownloadFileWithOras' }
Mock Set-ExitCode -MockWith {
Param($ExitCode, $ErrorMessage)
throw "Set-ExitCode:${ExitCode}:${ErrorMessage}"
return
}
}
AfterEach {
$global:BootstrapProfileContainerRegistryServer = $null
}

It 'returns early when out-of-tree credential provider flags are not configured' {
$global:KubeletConfigArgs = @("--address=0.0.0.0")

{ Install-CredentialProvider -KubeDir 'c:\k' -CustomCloudContainerRegistryDNSSuffix '' } | Should -Not -Throw
Assert-MockCalled -CommandName 'Config-CredentialProvider' -Times 0
$script:lastDownloadReference | Should -Be ""
Assert-MockCalled -CommandName 'DownloadFileOverHttp' -Times 0
}

It 'uses legacy binaries URL for non-ni cluster' {
$global:BootstrapProfileContainerRegistryServer = ""
$global:CredentialProviderURL = 'https://packages.aks.azure.com/cloud-provider-azure/v1.34.0/binaries/azure-acr-credential-provider-linux-amd64-v1.34.0.tar.gz'
Install-CredentialProvider -KubeDir 'c:\k' -CustomCloudContainerRegistryDNSSuffix ''
Comment thread
fseldow marked this conversation as resolved.
Assert-MockCalled -CommandName 'DownloadFileOverHttp' -Times 1
}
Comment thread
fseldow marked this conversation as resolved.

It 'uses version parsed from dalec URL for ORAS reference' {
Install-CredentialProvider -KubeDir 'c:\k' -CustomCloudContainerRegistryDNSSuffix ''
$script:lastDownloadReference | Should -Be 'myregistry.azurecr.io/aks/packages/kubernetes/azure-acr-credential-provider:v1.34.0'
}

It 'uses version parsed from legacy binaries URL for ORAS reference' {
$global:CredentialProviderURL = 'https://packages.aks.azure.com/cloud-provider-azure/v1.34.0/binaries/azure-acr-credential-provider-linux-amd64-v1.34.0.tar.gz'
Install-CredentialProvider -KubeDir 'c:\k' -CustomCloudContainerRegistryDNSSuffix ''
$script:lastDownloadReference | Should -Be 'myregistry.azurecr.io/aks/packages/kubernetes/azure-acr-credential-provider:v1.34.0'
}

It 'falls back to KubeBinariesVersion when URL contains no parseable version' {
$global:CredentialProviderURL = 'https://packages.aks.azure.com/invalid/credential-provider.zip'
Install-CredentialProvider -KubeDir 'c:\k' -CustomCloudContainerRegistryDNSSuffix ''
$script:lastDownloadReference | Should -Be 'myregistry.azurecr.io/aks/packages/kubernetes/azure-acr-credential-provider:v1.31.9'
}
}
Loading