Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
20aecd7
add pester tests for connectedk8s cli extension
Mar 18, 2025
40926a3
Pass the force delete param to the API call (#4)
atchutbarli Mar 25, 2025
8f068a8
fix CI testcases for nodepool image issues (#8)
bavneetsingh16 May 23, 2025
ad1ce1d
update python version to 3.13 (#12)
bavneetsingh16 Jul 31, 2025
2121694
changes to support gateway association/disassociation for api version…
bavneetsingh16 Sep 17, 2025
3c08eac
[Azure RBAC] Deprecate 3P mode flags, fix Azure RBAC enablement bug, …
vineeth-thumma Sep 29, 2025
ed5b5e2
Merge branch 'Azure:main' into main
bavneetsingh16 Oct 15, 2025
9038227
remove hardcoded public ARM endpoint url for fairfax and mooncake (#24)
bavneetsingh16 Oct 15, 2025
002220d
Bug Fix for FFX mcr url (#22)
hapate Oct 15, 2025
6c0a24b
[connectedk8s] update release notes and version (#26)
bavneetsingh16 Oct 16, 2025
3ca7ae1
Add Helm Overrides for AGC (#23)
junw98 Oct 27, 2025
bbebbcd
[Azure RBAC] Remove deprecated flags (#16)
vineeth-thumma Oct 28, 2025
979a4c3
update prediag version (#27)
atchutbarli Nov 4, 2025
09119b8
Updating the proxy version constant (#28)
gabemousa Nov 5, 2025
22d2042
update broken test in CI pipeline (#37)
bavneetsingh16 Mar 3, 2026
532f503
Update CLIENT_PROXY_VERSION to 1.3.033281 (#36)
shlokpatel57 Mar 3, 2026
92383b8
Updates in CSP version (#40)
shlokpatel57 Mar 26, 2026
11550d4
Add ARM64 support for Helm installation in connectedk8s (#34)
ashnanze Apr 15, 2026
c9215c4
Handle removal of --all flag in Helm 4 (#42)
atchutbarli Apr 20, 2026
83bb4a1
bump connectedk8s version to 1.12.0
ashnanze Apr 22, 2026
ae26c76
add connectedk8s-1.12.0 whl for E2E testing
ashnanze Apr 22, 2026
f35e56c
fix version to 1.11.1 and correct CLIENT_PROXY_VERSION in release notes
ashnanze Apr 22, 2026
bff5964
resolve merge conflicts with upstream main
ashnanze Apr 22, 2026
2c5d937
update whl to connectedk8s-1.11.1
ashnanze Apr 22, 2026
9cfe703
fix ARM64 helm binary: use arch-aware path in install_helm_client
ashnanze May 5, 2026
3a7900c
sync ARM64 helm fix from origin/main: add _resolve_helm_pull_target, …
ashnanze May 5, 2026
3a859d8
sync Helm 4 fix from origin/main: add get_helm_major_version, conditi…
ashnanze May 6, 2026
9178359
fix CLIENT_PROXY_VERSION to 1.3.033581 to match 1.11.1 changelog
ashnanze May 7, 2026
5e30207
update CLIENT_PROXY_VERSION to 1.3.033892 to match origin/main
ashnanze May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/connectedk8s/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

Release History
===============
1.11.1
+++++++
* Added ARM64 support for Helm binary installation.
* Handle removal of '--all' flag in Helm 4 to ensure compatibility.
* Added Helm overrides support for Application Gateway for Containers (AGC).
* Updated CSP version.
* Updated CLIENT_PROXY_VERSION to 1.3.033892.
* Updated pre-diagnostics version.

1.11.0
+++++
* [Breaking Change] Removed deprecated '--app-id' and '--app-secret' RBAC parameters from the extension.
Expand Down
4 changes: 2 additions & 2 deletions src/connectedk8s/azext_connectedk8s/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@
)
DNS_Check_Result_String = "DNS Result:"
AZ_CLI_ADAL_TO_MSAL_MIGRATE_VERSION = "2.30.0"
CLIENT_PROXY_VERSION = "1.3.032281"
CLIENT_PROXY_VERSION = "1.3.033892"
CLIENT_PROXY_FOLDER = ".clientproxy"
API_SERVER_PORT = 47011
CLIENT_PROXY_PORT = 47010
Expand All @@ -491,7 +491,7 @@
# URL constants
CLIENT_PROXY_MCR_TARGET = "azureconnectivity/proxy"
HELM_MCR_URL = "azurearck8s/helm"
HELM_VERSION = "v3.12.2"
HELM_VERSION = "v3.20.1"
Download_And_Install_Kubectl_Fault_Type = "Failed to download and install kubectl"
Azure_Access_Token_Variable = "AZURE_ACCESS_TOKEN"
Provisioned_Cluster_Kind = "provisionedcluster"
Expand Down
30 changes: 26 additions & 4 deletions src/connectedk8s/azext_connectedk8s/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,11 @@ def validate_connect_rp_location(cmd: CLICommand, location: str) -> None:
"Failed to fetch resource provider details",
)

for resourceTypes in providerDetails.resource_types: # type: ignore[attr-defined]
for resourceTypes in providerDetails.resource_types: # type: ignore[union-attr]
if resourceTypes.resource_type == "connectedClusters":
rp_locations = [
location.replace(" ", "").lower()
for location in resourceTypes.locations
for location in resourceTypes.locations # type: ignore[union-attr]
]
if location.lower() not in rp_locations:
telemetry.set_exception(
Expand Down Expand Up @@ -1315,7 +1315,7 @@ def helm_install_release(
]

# Special configurations from 2022-09-01 ARM metadata.
# "dataplaneEndpoints" property does not appear in arm_metadata structure for public and AGC clouds.
# "dataplaneEndpoints" does not appear in arm_metadata for public and AGC
if "dataplaneEndpoints" in arm_metadata:
if "arcConfigEndpoint" in arm_metadata["dataplaneEndpoints"]:
notification_endpoint = arm_metadata["dataplaneEndpoints"][
Expand Down Expand Up @@ -1484,6 +1484,25 @@ def redact_sensitive_fields_from_string(input_text: str) -> str:
return input_text


def get_helm_major_version(helm_client_location: str) -> int:
"""Returns the major version of the helm client (e.g. 3 or 4)."""
try:
result = Popen(
[helm_client_location, "version", "--short"],
stdout=PIPE,
stderr=PIPE,
)
out, _ = result.communicate()
version_str = out.decode("ascii").strip()
# version_str is like "v3.17.0+gabcdef" or "v4.1.3+gabcdef"
match = re.match(r"v(\d+)\.", version_str)
if match:
return int(match.group(1))
except (OSError, ValueError):
pass
return 3 # assume Helm 3 if we cannot determine version


def get_release_namespace(
kube_config: str | None,
kube_context: str | None,
Expand All @@ -1494,11 +1513,14 @@ def get_release_namespace(
cmd_helm_release = [
helm_client_location,
"list",
"-a",
"--all-namespaces",
"--output",
"json",
]
# Helm 4 removed the --all flag (all releases are shown by default).
# Helm 3 requires --all to include non-deployed releases.
if get_helm_major_version(helm_client_location) < 4:
cmd_helm_release.insert(2, "--all")
if kube_config:
cmd_helm_release.extend(["--kubeconfig", kube_config])
if kube_context:
Expand Down
161 changes: 148 additions & 13 deletions src/connectedk8s/azext_connectedk8s/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,134 @@ def check_kube_connection() -> str:
assert False


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


def install_helm_client(cmd: CLICommand) -> str:
print(
f"Step: {utils.get_utctimestring()}: Install Helm client if it does not exist"
Expand All @@ -1263,6 +1391,7 @@ def install_helm_client(cmd: CLICommand) -> str:
# Fetch system related info
operating_system = platform.system().lower()
machine_type = platform.machine()
arch = "arm64" if machine_type.lower() in ("aarch64", "arm64") else "amd64"

# Send machine telemetry
telemetry.add_extension_event(
Expand All @@ -1271,20 +1400,18 @@ def install_helm_client(cmd: CLICommand) -> str:
# Set helm binary download & install locations
if operating_system == "windows":
download_location_string = f".azure\\helm\\{consts.HELM_VERSION}"
download_file_name = f"helm-{consts.HELM_VERSION}-{operating_system}-amd64.zip"
download_file_name = f"helm-{consts.HELM_VERSION}-{operating_system}-{arch}.zip"
install_location_string = (
f".azure\\helm\\{consts.HELM_VERSION}\\{operating_system}-amd64\\helm.exe"
f".azure\\helm\\{consts.HELM_VERSION}\\{operating_system}-{arch}\\helm.exe"
)
artifactTag = f"helm-{consts.HELM_VERSION}-{operating_system}-amd64"
elif operating_system == "linux" or operating_system == "darwin":
download_location_string = f".azure/helm/{consts.HELM_VERSION}"
download_file_name = (
f"helm-{consts.HELM_VERSION}-{operating_system}-amd64.tar.gz"
f"helm-{consts.HELM_VERSION}-{operating_system}-{arch}.tar.gz"
)
install_location_string = (
f".azure/helm/{consts.HELM_VERSION}/{operating_system}-amd64/helm"
f".azure/helm/{consts.HELM_VERSION}/{operating_system}-{arch}/helm"
)
artifactTag = f"helm-{consts.HELM_VERSION}-{operating_system}-amd64"
else:
telemetry.set_exception(
exception="Unsupported OS for installing helm client",
Expand All @@ -1296,15 +1423,15 @@ def install_helm_client(cmd: CLICommand) -> str:
)

download_location = os.path.expanduser(os.path.join("~", download_location_string))
download_dir = os.path.dirname(download_location)
install_location = os.path.expanduser(os.path.join("~", install_location_string))

# Download compressed Helm binary if not already present
if not os.path.isfile(install_location):
# Creating the helm folder if it doesnt exist
if not os.path.exists(download_dir):
# The archive is downloaded to ~/.azure/helm/<version>/<archive-file>.
# Ensure the <version> directory exists first to avoid file-not-found errors.
if not os.path.exists(download_location):
try:
os.makedirs(download_dir)
os.makedirs(download_location)
except Exception as e:
telemetry.set_exception(
exception=e,
Expand All @@ -1318,15 +1445,23 @@ def install_helm_client(cmd: CLICommand) -> str:
"Downloading helm client for first time. This can take few minutes..."
)

retry_count = 3
retry_delay = 5
# Helm binaries are downloaded from MCR artifacts for all architectures.
mcr_url = utils.get_mcr_path(cmd.cli_ctx.cloud.endpoints.active_directory)

client = oras.client.OrasClient(hostname=mcr_url)
retry_count = 3
retry_delay = 5
pull_target = _resolve_helm_pull_target(
mcr_url,
consts.HELM_MCR_URL,
consts.HELM_VERSION,
operating_system,
arch,
)
for i in range(retry_count):
try:
client.pull(
target=f"{mcr_url}/{consts.HELM_MCR_URL}:{artifactTag}",
target=pull_target,
outdir=download_location,
)
break
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion src/connectedk8s/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# TODO: Confirm this is the right version number you want and it matches your
# HISTORY.rst entry.

VERSION = "1.11.0"
VERSION = "1.11.1"

# The full list of classifiers is available at
# https://pypi.python.org/pypi?%3Aaction=list_classifiers
Expand Down
Loading