Add ARM64 support for Helm installation in connectedk8s#9815
Add ARM64 support for Helm installation in connectedk8s#9815ashnanze wants to merge 19 commits intoAzure:mainfrom
Conversation
* forcedelete * format * add code owner * mypy
… '2025-08-01-preview' (#17)
…add E2E coverage and improve logging (#20) * add pester tests for connectedk8s cli extension * Pass the force delete param to the API call (#4) * forcedelete * format * add code owner * mypy * Parameterize for airgapped clouds (#5) * Add parameterization for the airgapped clouds * Fix azdev style * MCR path function * azdev, ruff, and mypy --------- Co-authored-by: Matthew McNeal (from Dev Box) <mmcneal@microsoft.com> * Oras client fix to work with different MCRs (#6) Co-authored-by: mmcneal <mmcneal@microsoft.com> * fix CI testcases for nodepool image issues (#8) * update errors for the config and connectivity issues (#11) * update errors * format * style * update python version to 3.13 (#12) * Update cluster diagnostics image to 1.29.3 (#7) * Update cluster diagnostics helm chart to 1.29.3 * Fix lint issues --------- Co-authored-by: bgriddaluru <bharath.griddaluru@microsoft.com> * RBAC deprecation & fix the issue * typo * fix comments * update tests * add pester tests for connectedk8s cli extension * Pass the force delete param to the API call (#4) * forcedelete * format * add code owner * mypy * fix CI testcases for nodepool image issues (#8) * update errors for the config and connectivity issues (#11) * update errors * format * style * update python version to 3.13 (#12) * rebase * fix tests * fix version * fix mypy, lint * fix test * fix test * fix test * fix test * fix test * rename test * deprecate flags * rebase * rebase * bump version for release --------- Co-authored-by: Bavneet Singh <bavneetsingh@microsoft.com> Co-authored-by: Atchut Kumar Barli <atchut@gmail.com> Co-authored-by: mcnealm13 <57726243+mcnealm13@users.noreply.github.com> Co-authored-by: Matthew McNeal (from Dev Box) <mmcneal@microsoft.com> Co-authored-by: Bavneet Singh <33008256+bavneetsingh16@users.noreply.github.com> Co-authored-by: bgriddaluru <117554445+bgriddaluru@users.noreply.github.com> Co-authored-by: bgriddaluru <bharath.griddaluru@microsoft.com> Co-authored-by: vithumma <vithumma@microsoft.com>
* add agc overrides * update gns endpoint * add indentation * fix linter error * fix ruff formatting * move overrides to it's own method * update method * update ruff formatting
* add pester tests for connectedk8s cli extension * Pass the force delete param to the API call (#4) * forcedelete * format * add code owner * mypy * Parameterize for airgapped clouds (#5) * Add parameterization for the airgapped clouds * Fix azdev style * MCR path function * azdev, ruff, and mypy --------- Co-authored-by: Matthew McNeal (from Dev Box) <mmcneal@microsoft.com> * Oras client fix to work with different MCRs (#6) Co-authored-by: mmcneal <mmcneal@microsoft.com> * fix CI testcases for nodepool image issues (#8) * update errors for the config and connectivity issues (#11) * update errors * format * style * update python version to 3.13 (#12) * Update cluster diagnostics image to 1.29.3 (#7) * Update cluster diagnostics helm chart to 1.29.3 * Fix lint issues --------- Co-authored-by: bgriddaluru <bharath.griddaluru@microsoft.com> * RBAC deprecation & fix the issue * typo * fix comments * update tests * add pester tests for connectedk8s cli extension * Pass the force delete param to the API call (#4) * forcedelete * format * add code owner * mypy * fix CI testcases for nodepool image issues (#8) * update errors for the config and connectivity issues (#11) * update errors * format * style * update python version to 3.13 (#12) * rebase * fix tests * fix version * fix mypy, lint * fix test * fix test * fix test * fix test * fix test * rename test * add pester tests for connectedk8s cli extension * Pass the force delete param to the API call (#4) * forcedelete * format * add code owner * mypy * fix CI testcases for nodepool image issues (#8) * update python version to 3.13 (#12) * changes to support gateway association/disassociation for api version '2025-08-01-preview' (#17) * [Azure RBAC] Deprecate 3P mode flags, fix Azure RBAC enablement bug, add E2E coverage and improve logging (#20) * add pester tests for connectedk8s cli extension * Pass the force delete param to the API call (#4) * forcedelete * format * add code owner * mypy * Parameterize for airgapped clouds (#5) * Add parameterization for the airgapped clouds * Fix azdev style * MCR path function * azdev, ruff, and mypy --------- Co-authored-by: Matthew McNeal (from Dev Box) <mmcneal@microsoft.com> * Oras client fix to work with different MCRs (#6) Co-authored-by: mmcneal <mmcneal@microsoft.com> * fix CI testcases for nodepool image issues (#8) * update errors for the config and connectivity issues (#11) * update errors * format * style * update python version to 3.13 (#12) * Update cluster diagnostics image to 1.29.3 (#7) * Update cluster diagnostics helm chart to 1.29.3 * Fix lint issues --------- Co-authored-by: bgriddaluru <bharath.griddaluru@microsoft.com> * RBAC deprecation & fix the issue * typo * fix comments * update tests * add pester tests for connectedk8s cli extension * Pass the force delete param to the API call (#4) * forcedelete * format * add code owner * mypy * fix CI testcases for nodepool image issues (#8) * update errors for the config and connectivity issues (#11) * update errors * format * style * update python version to 3.13 (#12) * rebase * fix tests * fix version * fix mypy, lint * fix test * fix test * fix test * fix test * fix test * rename test * deprecate flags * rebase * rebase * bump version for release --------- Co-authored-by: Bavneet Singh <bavneetsingh@microsoft.com> Co-authored-by: Atchut Kumar Barli <atchut@gmail.com> Co-authored-by: mcnealm13 <57726243+mcnealm13@users.noreply.github.com> Co-authored-by: Matthew McNeal (from Dev Box) <mmcneal@microsoft.com> Co-authored-by: Bavneet Singh <33008256+bavneetsingh16@users.noreply.github.com> Co-authored-by: bgriddaluru <117554445+bgriddaluru@users.noreply.github.com> Co-authored-by: bgriddaluru <bharath.griddaluru@microsoft.com> Co-authored-by: vithumma <vithumma@microsoft.com> * remove breaking change announcement for removed flags --------- Co-authored-by: Bavneet Singh <bavneetsingh@microsoft.com> Co-authored-by: Atchut Kumar Barli <atchut@gmail.com> Co-authored-by: mcnealm13 <57726243+mcnealm13@users.noreply.github.com> Co-authored-by: Matthew McNeal (from Dev Box) <mmcneal@microsoft.com> Co-authored-by: Bavneet Singh <33008256+bavneetsingh16@users.noreply.github.com> Co-authored-by: bgriddaluru <117554445+bgriddaluru@users.noreply.github.com> Co-authored-by: bgriddaluru <bharath.griddaluru@microsoft.com> Co-authored-by: vithumma <vithumma@microsoft.com>
* adjusting arm64 support * editing * adding * Revert k8s-extension changes from this repo * changes * changing order * fix ruff: ternary for arch, remove unused artifactTag * fix ruff format * fix mypy type: ignore comments in _utils.py * raise CLIInternalError instead of silent fallbacks in _resolve_helm_pull_target * use requests instead of oras internals for manifest resolution * add MCR anonymous bearer token auth for manifest resolution * fix MCR resolution: add Accept header, fetch child manifests for annotations
the tests passed, the typecheck and extension index failures are not relevant to this PR * switch * fixswitch * addhelm4test * specificexception
|
Validation for Breaking Change Starting...
Thanks for your contribution! |
|
Thank you for your contribution! We will review the pull request and get back to you soon. |
|
The git hooks are available for azure-cli and azure-cli-extensions repos. They could help you run required checks before creating the PR. Please sync the latest code with latest dev branch (for azure-cli) or main branch (for azure-cli-extensions). pip install azdev --upgrade
azdev setup -c <your azure-cli repo path> -r <your azure-cli-extensions repo path>
|
CodeGen Tools Feedback CollectionThank you for using our CodeGen tool. We value your feedback, and we would like to know how we can improve our product. Please take a few minutes to fill our codegen survey |
|
Hi @ashnanze
|
There was a problem hiding this comment.
Pull request overview
This PR updates the connectedk8s extension’s Helm installation flow to support ARM64 hosts by selecting the correct architecture-specific Helm artifacts from MCR, while also bumping the embedded Helm version and adding an integration-test harness + pipeline assets under testing/.
Changes:
- Bump Helm from
v3.12.2tov3.20.1and detect host architecture (amd64vsarm64) for Helm artifact selection and install paths. - Add
_resolve_helm_pull_target()to resolve an arch-specific OCI tag first and fall back to a manifest-list lookup. - Introduce a
testing/Pester-based test suite and Azure DevOps pipelines/templates for running connectedk8s scenarios.
Reviewed changes
Copilot reviewed 31 out of 35 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| testing/test/helper/Constants.ps1 | Adds shared constants/config loading for Pester scenarios. |
| testing/test/configurations/WorkloadIdentity.Tests.ps1 | Adds workload identity onboarding/update/delete scenario coverage. |
| testing/test/configurations/Troubleshoot.Tests.ps1 | Adds troubleshoot command scenario coverage. |
| testing/test/configurations/Proxy.Tests.ps1 | Adds proxy enable/disable scenario coverage. |
| testing/test/configurations/Gateway.Tests.ps1 | Adds gateway enable/disable scenario coverage. |
| testing/test/configurations/ForcedDelete.Tests.ps1 | Adds forced delete scenario coverage. |
| testing/test/configurations/EnableDisableFeatures.Tests.ps1 | Adds enable/disable features scenario coverage with helpers. |
| testing/test/configurations/ConnectProxy.Tests.ps1 | Adds connectedk8s proxy scenario coverage including kubeconfig validation. |
| testing/test/configurations/BasicOnboarding.Tests.ps1 | Adds basic onboarding + auto-upgrade toggle scenario coverage. |
| testing/test/configurations/AutoUpdate.Tests.ps1 | Adds auto-upgrade disabled/enabled scenario coverage. |
| testing/settings.template.json | Adds template config file used by the new test harness. |
| testing/pipeline/templates/run-test.yml | Adds ADO job template to build/install extension and run Pester scenarios. |
| testing/pipeline/k8s-custom-pipelines.yml | Adds a multi-job pipeline to run the new scenario tests and checks. |
| testing/owners.txt | Adds owners list for the testing area. |
| testing/bin/k8s_configuration-1.0.0-py3-none-any.whl | Adds a pinned wheel used by the test harness. |
| testing/bin/connectedk8s-values.yaml | Adds default values file used by the test harness. |
| testing/Test.ps1 | Adds the main Pester runner script for CI/local execution. |
| testing/README.md | Documents how to set up and run the new test suite. |
| testing/Bootstrap.ps1 | Adds bootstrap script for test environment preparation. |
| testing/.gitignore | Ignores runtime-generated test artifacts and most bin/ contents with allowlist exceptions. |
| src/k8s-extension/azext_k8s_extension/utils.py | Refactors get_mcr_path to accept an endpoint string and adjusts logic for gov/china. |
| src/k8s-extension/azext_k8s_extension/custom.py | Updates callers to the new get_mcr_path(active_directory_endpoint) signature. |
| src/connectedk8s/setup.py | Bumps extension version to 1.10.12. |
| src/connectedk8s/azext_connectedk8s/tests/unittests/test_utils_.py | Adds unit tests for the refactored get_mcr_path. |
| src/connectedk8s/azext_connectedk8s/custom.py | Adds ARM64 arch detection, Helm v3.20.1 update, directory-creation fix, and _resolve_helm_pull_target() manifest logic. |
| src/connectedk8s/azext_connectedk8s/clientproxyhelper/_binaryutils.py | Updates caller to use new get_mcr_path(active_directory_endpoint) signature. |
| src/connectedk8s/azext_connectedk8s/_utils.py | Refactors get_mcr_path, adds Helm 4 compatibility for helm list, and adds AGC endpoint overrides helper. |
| src/connectedk8s/azext_connectedk8s/_precheckutils.py | Updates caller to use new get_mcr_path(active_directory_endpoint) signature. |
| src/connectedk8s/azext_connectedk8s/_params.py | Removes deprecated --app-id/--app-secret arguments from enable-features. |
| src/connectedk8s/azext_connectedk8s/_constants.py | Updates Helm version and bumps related artifact versions used by the extension. |
| src/connectedk8s/azext_connectedk8s/_client_factory.py | Removes hardcoded ARM base_url to allow sovereign clouds to use the correct endpoint. |
| src/connectedk8s/azext_connectedk8s/_breaking_change.py | Removes breaking-change registration for now-removed deprecated args. |
| src/connectedk8s/HISTORY.rst | Adds release notes for 1.10.11 and 1.10.12. |
| Write-Host "Removing the old connectedk8s extension..." | ||
| az extension remove -n connectedk8s | ||
| Write-Host "Installing connectedk8s version $connectedk8sVersion..." | ||
| az extension add --source ./bin/connectedk8s-$connectedk8sVersion-py2.py3-none-any.whl |
There was a problem hiding this comment.
The install path assumes the wheel filename format connectedk8s-<version>-py2.py3-none-any.whl, but the committed sample wheel in testing/bin/ is named connectedk8s-1.0.0-py3-none-any.whl (and settings.template.json defaults to 1.0.0). This mismatch will cause local runs to fail to find the wheel. Consider either (1) updating the committed wheel name / template version to match the expected tag, or (2) making the script glob for connectedk8s-$connectedk8sVersion-*.whl instead of hardcoding the tag.
| az extension add --source ./bin/connectedk8s-$connectedk8sVersion-py2.py3-none-any.whl | |
| $wheelPattern = "connectedk8s-$connectedk8sVersion-*.whl" | |
| $wheelFiles = Get-ChildItem -Path "$PSScriptRoot/bin" -Filter $wheelPattern -File | |
| if ($wheelFiles.Count -eq 0) { | |
| throw "Could not find a connectedk8s wheel matching '$wheelPattern' in '$PSScriptRoot/bin'." | |
| } | |
| if ($wheelFiles.Count -gt 1) { | |
| throw "Found multiple connectedk8s wheels matching '$wheelPattern' in '$PSScriptRoot/bin': $($wheelFiles.Name -join ', ')." | |
| } | |
| az extension add --source $wheelFiles[0].FullName |
| if ($ParallelCI) { | ||
| # This runs the tests in parallel during the CI pipline to speed up testing | ||
|
|
||
| Write-Host "Invoking Pester to run tests from '$testFilePath'..." |
There was a problem hiding this comment.
In the ParallelCI branch, the log message references $testFilePath, but only $testFilePaths is defined earlier. This will print an empty value and makes the script harder to debug; if later logic depends on it, it can also break. Use the correct variable (or set $testFilePath explicitly) before referencing it.
| Write-Host "Invoking Pester to run tests from '$testFilePath'..." | |
| Write-Host "Invoking Pester to run tests from '$testFilePaths'..." |
| $testName = Split-Path $testFile –leaf | ||
| Start-Job -ArgumentList $testName, $testFile, $resultFileNumber, $TestFileDirectory -Name $testName -ScriptBlock { | ||
| param($name, $testFile, $resultFileNumber, $testFileDirectory) | ||
|
|
||
| Write-Host "$testFile to result file #$resultFileNumber" | ||
| $testResult = Invoke-Pester $testFile -Passthru -Output Detailed | ||
| $testResult | Export-JUnitReport -Path "$testFileDirectory/$name.xml" | ||
| } | ||
| } | ||
|
|
||
| do { | ||
| Write-Host ">> Still running tests @ $(Get-Date –Format "HH:mm:ss")" –ForegroundColor Blue | ||
| Get-Job | Where-Object { $_.State -eq "Running" } | Format-Table –AutoSize | ||
| Start-Sleep –Seconds 30 |
There was a problem hiding this comment.
Several parameters use a Unicode en dash (e.g. –leaf, –Format, –AutoSize, –Seconds, –AutoRemoveJob, –Wait, –ForegroundColor) instead of the ASCII hyphen-minus -. PowerShell treats these as different characters, so these calls will fail at runtime. Replace them with standard -leaf, -Format, etc.
| # OCI media types required by MCR (HEAD/GET return 404 without Accept). | ||
| oci_accept = ( | ||
| "application/vnd.oci.image.manifest.v1+json, " | ||
| "application/vnd.oci.image.index.v1+json" | ||
| ) | ||
|
|
||
| # Check whether the arch-specific tag exists. | ||
| try: | ||
| response = http_client.head( | ||
| f"{base_api}/{arch_specific_tag}", | ||
| headers={"Accept": oci_accept}, | ||
| timeout=30, | ||
| ) | ||
| if response.status_code == 200: | ||
| return arch_specific_target | ||
| logger.debug( | ||
| "Arch-specific tag %s returned HTTP %d; trying manifest list.", | ||
| arch_specific_tag, | ||
| response.status_code, | ||
| ) | ||
| except Exception as e: # pylint: disable=broad-except | ||
| logger.debug( | ||
| "Arch-specific tag check failed (%s); trying manifest list.", | ||
| e, | ||
| ) | ||
|
|
||
| # Fall back to the manifest list tag and match via annotation title. | ||
| # Annotations live on each child manifest, not on the index entries, | ||
| # so we must fetch every child manifest to find the right one. | ||
| manifest_list_tag = f"helm-{helm_version}" | ||
| expected_title_prefix = f"helm-{helm_version}-{operating_system}-{arch}" | ||
| try: | ||
| response = http_client.get( | ||
| f"{base_api}/{manifest_list_tag}", | ||
| headers={"Accept": oci_accept}, | ||
| timeout=30, | ||
| ) | ||
| if response.status_code != 200: | ||
| raise CLIInternalError( | ||
| f"Could not resolve helm binary for {operating_system}/{arch}. " | ||
| f"Arch-specific tag '{arch_specific_tag}' check failed and " | ||
| f"manifest list '{manifest_list_tag}' returned HTTP {response.status_code}." | ||
| ) |
There was a problem hiding this comment.
_resolve_helm_pull_target queries the registry HTTP API with requests but does not handle the common 401/WWW-Authenticate challenge flow (token retrieval) used by Docker/OCI registries. If MCR requires auth (or changes behavior), this function will always fail even though oras could have pulled successfully. Consider resolving the target by attempting oras pull for the arch-specific tag first (and only falling back on error), or implement the bearer-token flow for registry API calls.
| # OCI media types required by MCR (HEAD/GET return 404 without Accept). | ||
| oci_accept = ( | ||
| "application/vnd.oci.image.manifest.v1+json, " | ||
| "application/vnd.oci.image.index.v1+json" |
There was a problem hiding this comment.
The Accept header only advertises OCI media types. Some registries (and some MCR artifacts) return Docker v2 manifest list/media types, and will respond with 404/406 if those aren’t included. To make this more robust, include the Docker media types as well (e.g. application/vnd.docker.distribution.manifest.v2+json and application/vnd.docker.distribution.manifest.list.v2+json) or handle content negotiation based on the Content-Type response header.
| # OCI media types required by MCR (HEAD/GET return 404 without Accept). | |
| oci_accept = ( | |
| "application/vnd.oci.image.manifest.v1+json, " | |
| "application/vnd.oci.image.index.v1+json" | |
| # Registry responses may use either OCI or Docker schema 2 media types; | |
| # advertise both so HEAD/GET requests work across MCR artifacts and | |
| # compatible registries. | |
| oci_accept = ( | |
| "application/vnd.oci.image.manifest.v1+json, " | |
| "application/vnd.oci.image.index.v1+json, " | |
| "application/vnd.docker.distribution.manifest.v2+json, " | |
| "application/vnd.docker.distribution.manifest.list.v2+json" |
| arm_metadata_endpoint_array = ( | ||
| arm_metadata["authentication"]["loginEndpoint"].strip("/").split(".") | ||
| ) | ||
| if len(arm_metadata_endpoint_array) < 4: | ||
| raise CLIInternalError("Unexpected loginEndpoint format for AGC") | ||
|
|
||
| cloud_suffix = arm_metadata_endpoint_array[3] | ||
| endpoint_suffix = ( | ||
| arm_metadata_endpoint_array[2] + "." + arm_metadata_endpoint_array[3] | ||
| ) | ||
| if cloud_name.lower() == "usnat": | ||
| cloud_suffix = ( | ||
| arm_metadata_endpoint_array[2] | ||
| + "." | ||
| + arm_metadata_endpoint_array[3] | ||
| + "." | ||
| + arm_metadata_endpoint_array[4] | ||
| ) |
There was a problem hiding this comment.
add_agc_endpoint_overrides indexes arm_metadata_endpoint_array[4] in the usnat branch but only validates len(...) < 4. If loginEndpoint splits into exactly 4 segments for usnat, this will raise IndexError and break onboarding. Add an explicit length check for the usnat format (>= 5) and/or parse the URL host with urllib.parse.urlparse before splitting.
| if ($provisioningState -eq $SUCCEEDED) { | ||
| $isProxyEnabled = helm get values -n azure-arc-release azure-arc -o yaml | grep isProxyEnabled | ||
| Write-Host "$isProxyEnabled" | ||
| if ($isProxyEnabled -match "isProxyEnabled: false") { | ||
| break | ||
| } | ||
| break | ||
| } |
There was a problem hiding this comment.
In the 'Disable proxy' retry loop, there is an unconditional break inside the $provisioningState -eq $SUCCEEDED block (line 49). This exits the loop even when isProxyEnabled is not yet false, so the test can pass without actually validating the update took effect. Remove the unconditional break (or move it into an else branch) so the loop only exits when the expected Helm value is observed or retries are exhausted.
| def _resolve_helm_pull_target( | ||
| mcr_url: str, | ||
| helm_mcr_repo: str, | ||
| helm_version: str, | ||
| operating_system: str, | ||
| arch: str, | ||
| ) -> str: | ||
| """Return the ORAS pull target for the helm binary. | ||
|
|
||
| Tries the arch-specific tag first (e.g. ``helm-v3.20.1-linux-arm64``). | ||
| If that tag does not exist, falls back to the manifest list tag | ||
| (``helm-v3.20.1``) and resolves the correct entry by matching the | ||
| ``org.opencontainers.image.title`` annotation on each child manifest. | ||
|
|
||
| Uses the OCI Distribution v2 HTTP API directly so that the logic is | ||
| independent of the ``oras`` library version installed. | ||
|
|
||
| :param mcr_url: MCR hostname (e.g. ``mcr.microsoft.com``) | ||
| :param helm_mcr_repo: repository path within MCR (e.g. ``azurearck8s/helm``) | ||
| :param helm_version: helm version string including the leading ``v`` (e.g. ``v3.20.1``) | ||
| :param operating_system: lower-case OS name: ``linux``, ``darwin``, or ``windows`` | ||
| :param arch: CPU architecture: ``amd64`` or ``arm64`` | ||
| :returns: full ORAS pull target string (tag-based or digest-based) | ||
| """ | ||
| import requests as http_client # pylint: disable=import-outside-toplevel | ||
|
|
||
| arch_specific_tag = f"helm-{helm_version}-{operating_system}-{arch}" | ||
| arch_specific_target = f"{mcr_url}/{helm_mcr_repo}:{arch_specific_tag}" | ||
| base_api = f"https://{mcr_url}/v2/{helm_mcr_repo}/manifests" | ||
|
|
||
| # OCI media types required by MCR (HEAD/GET return 404 without Accept). | ||
| oci_accept = ( | ||
| "application/vnd.oci.image.manifest.v1+json, " | ||
| "application/vnd.oci.image.index.v1+json" | ||
| ) | ||
|
|
||
| # Check whether the arch-specific tag exists. | ||
| try: | ||
| response = http_client.head( | ||
| f"{base_api}/{arch_specific_tag}", | ||
| headers={"Accept": oci_accept}, | ||
| timeout=30, | ||
| ) | ||
| if response.status_code == 200: | ||
| return arch_specific_target | ||
| logger.debug( | ||
| "Arch-specific tag %s returned HTTP %d; trying manifest list.", | ||
| arch_specific_tag, | ||
| response.status_code, | ||
| ) | ||
| except Exception as e: # pylint: disable=broad-except | ||
| logger.debug( | ||
| "Arch-specific tag check failed (%s); trying manifest list.", | ||
| e, | ||
| ) | ||
|
|
||
| # Fall back to the manifest list tag and match via annotation title. | ||
| # Annotations live on each child manifest, not on the index entries, | ||
| # so we must fetch every child manifest to find the right one. | ||
| manifest_list_tag = f"helm-{helm_version}" | ||
| expected_title_prefix = f"helm-{helm_version}-{operating_system}-{arch}" | ||
| try: | ||
| response = http_client.get( | ||
| f"{base_api}/{manifest_list_tag}", | ||
| headers={"Accept": oci_accept}, | ||
| timeout=30, | ||
| ) | ||
| if response.status_code != 200: | ||
| raise CLIInternalError( | ||
| f"Could not resolve helm binary for {operating_system}/{arch}. " | ||
| f"Arch-specific tag '{arch_specific_tag}' check failed and " | ||
| f"manifest list '{manifest_list_tag}' returned HTTP {response.status_code}." | ||
| ) | ||
|
|
||
| index = response.json() | ||
| for entry in index.get("manifests", []): | ||
| # Check platform fields if present (future-proof). | ||
| plat = entry.get("platform", {}) | ||
| if plat.get("os") == operating_system and plat.get("architecture") == arch: | ||
| digest = entry["digest"] | ||
| logger.debug( | ||
| "Resolved %s/%s via platform field to digest %s.", | ||
| operating_system, | ||
| arch, | ||
| digest, | ||
| ) | ||
| return f"{mcr_url}/{helm_mcr_repo}@{digest}" | ||
|
|
||
| # Annotations are on child manifests; fetch each one to match. | ||
| for entry in index.get("manifests", []): | ||
| digest = entry.get("digest", "") | ||
| try: | ||
| child_resp = http_client.get( | ||
| f"{base_api}/{digest}", | ||
| headers={"Accept": oci_accept}, | ||
| timeout=30, | ||
| ) | ||
| if child_resp.status_code != 200: | ||
| continue | ||
| child = child_resp.json() | ||
| title = child.get("annotations", {}).get( | ||
| "org.opencontainers.image.title", "" | ||
| ) | ||
| if title.startswith(expected_title_prefix): | ||
| logger.debug( | ||
| "Resolved %s/%s via child annotation title '%s' to digest %s.", | ||
| operating_system, | ||
| arch, | ||
| title, | ||
| digest, | ||
| ) | ||
| return f"{mcr_url}/{helm_mcr_repo}@{digest}" | ||
| except Exception: # pylint: disable=broad-except | ||
| continue | ||
|
|
||
| raise CLIInternalError( | ||
| f"Could not resolve helm binary for {operating_system}/{arch}. " | ||
| f"No matching entry found in manifest list '{manifest_list_tag}'." | ||
| ) | ||
| except CLIInternalError: | ||
| raise | ||
| except Exception as e: # pylint: disable=broad-except | ||
| raise CLIInternalError( | ||
| f"Could not resolve helm binary for {operating_system}/{arch}. " | ||
| f"Manifest list resolution failed: {e}" | ||
| ) from e |
There was a problem hiding this comment.
New helper _resolve_helm_pull_target introduces non-trivial logic (HTTP probing, tag fallback, manifest/index parsing). There are existing unit tests under azext_connectedk8s/tests/unittests/, but this behavior is currently untested. Adding unit tests with mocked HTTP responses (arch tag exists / missing, manifest list with platform fields, manifest list requiring child annotation fetch, error paths) would help prevent regressions.
Summary
v3.12.2tov3.20.1.platform.machine()(mapsaarch64/arm64toarm64, defaults toamd64).amd64)._resolve_helm_pull_target()to resolve the correct OCI pull target: tries arch-specific tag first (helm-v3.20.1-linux-arm64), falls back to manifest list tag (helm-v3.20.1) with annotation-based matching.Why
az connectedk8s connectto work on ARM64 machines by downloading the correct Helm binary.Validation
.whl.az connectedk8s connect.Directory handling note
~/.azure/helm/<version>/<archive-file>.~/.azure/helm, not the required version subdirectory~/.azure/helm/<version>.~/.azure/helm/<version>was missing, even if~/.azure/helmalready existed.<version>directory had already been created by a prior run.download_location(~/.azure/helm/<version>) directly before download.