Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@

/src/ip-group/ @necusjz @kairu-ms @jsntcy

/src/connectedk8s/ @bavneetsingh16 @deeksha345 @anagg929
/src/connectedk8s/ @bavneetsingh16 @deeksha345 @anagg929 @atchutbarli

/src/storagesync/ @jsntcy

Expand Down
41 changes: 27 additions & 14 deletions src/connectedk8s/azext_connectedk8s/_client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@

from azext_connectedk8s.vendored_sdks import ConnectedKubernetesClient
from azext_connectedk8s.vendored_sdks.operations import ConnectedClusterOperations
from azext_connectedk8s.vendored_sdks.preview_2024_07_01 import (
ConnectedKubernetesClient as ConnectedKubernetesClient20240701,
from azext_connectedk8s.vendored_sdks.preview_2025_08_01 import (
KubernetesClient as ConnectedKubernetesClient20250801,
)
from azext_connectedk8s.vendored_sdks.preview_2024_07_01.operations import (
ConnectedClusterOperations as ConnectedClusterOperations20240701,
from azext_connectedk8s.vendored_sdks.preview_2025_08_01.operations import (
ConnectedClusterOperations as ConnectedClusterOperations20250801,
)

AccessToken = namedtuple("AccessToken", ["token", "expires_on"])
Expand Down Expand Up @@ -61,34 +61,47 @@ def cf_connected_cluster(cli_ctx: AzCli, _: Any) -> ConnectedClusterOperations:
return cf_connectedk8s(cli_ctx).connected_cluster


def cf_connectedk8s_prev_2024_07_01(
def cf_connectedk8s_prev_2025_08_01(
cli_ctx: AzCli, *_: Any
) -> ConnectedKubernetesClient20240701:
from azext_connectedk8s.vendored_sdks.preview_2024_07_01 import (
ConnectedKubernetesClient,
) -> ConnectedKubernetesClient20250801:
from azext_connectedk8s.vendored_sdks.preview_2025_08_01 import (
KubernetesClient,
)
from azure.core.pipeline.policies import HeadersPolicy

client: ConnectedKubernetesClient
# Create custom headers policy for PUT requests
headers_policy = HeadersPolicy({
"x-ms-azurearc-cli": "true"
})

client: KubernetesClient
access_token = os.getenv(consts.Azure_Access_Token_Variable)
if access_token is not None:
validate_custom_token()
credential = AccessTokenCredential(access_token=access_token)
client = get_mgmt_service_client(
cli_ctx,
ConnectedKubernetesClient,
KubernetesClient,
subscription_id=os.getenv("AZURE_SUBSCRIPTION_ID"),
credential=credential,
base_url="https://management.azure.com",
per_call_policies=[headers_policy],
)
return client

client = get_mgmt_service_client(cli_ctx, ConnectedKubernetesClient)
client = get_mgmt_service_client(
cli_ctx,
KubernetesClient,
base_url="https://management.azure.com",
per_call_policies=[headers_policy],
)
return client


def cf_connected_cluster_prev_2024_07_01(
def cf_connected_cluster_prev_2025_08_01(
cli_ctx: AzCli, _: Any
) -> ConnectedClusterOperations20240701:
return cf_connectedk8s_prev_2024_07_01(cli_ctx).connected_cluster
) -> ConnectedClusterOperations20250801:
return cf_connectedk8s_prev_2025_08_01(cli_ctx).connected_cluster


def cf_connectedmachine(
Expand Down
34 changes: 24 additions & 10 deletions src/connectedk8s/azext_connectedk8s/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@
AHB_Enum_Values = ["True", "False", "NotApplicable"]
Feature_Values = ["cluster-connect", "azure-rbac", "custom-locations"]
CRD_FOR_FORCE_DELETE = [
"arccertificates.clusterconfig.azure.com",
"azureclusteridentityrequests.clusterconfig.azure.com",
"azureextensionidentities.clusterconfig.azure.com",
"connectedclusters.arc.azure.com",
"customlocationsettings.clusterconfig.azure.com",
"extensionconfigs.clusterconfig.azure.com",
"gitconfigs.clusterconfig.azure.com",
"arccertificates.clusterconfig.azure",
"azureclusteridentityrequests.clusterconfig.azure",
"azureextensionidentities.clusterconfig.azure",
"connectedclusters.arc.azure",
"customlocationsettings.clusterconfig.azure",
"extensionconfigs.clusterconfig.azure",
"gitconfigs.clusterconfig.azure",
]
Helm_Install_Release_Userfault_Messages = [
"forbidden",
Expand Down Expand Up @@ -418,7 +418,7 @@

# Connect Precheck Diagnoser constants
Cluster_Diagnostic_Checks_Job_Registry_Path = (
"mcr.microsoft.com/azurearck8s/helmchart/stable/clusterdiagnosticchecks:0.2.2"
"azurearck8s/helmchart/stable/clusterdiagnosticchecks:1.29.3"
)
Cluster_Diagnostic_Checks_Helm_Install_Failed_Fault_Type = (
"Error while installing cluster diagnostic checks helm release"
Expand Down Expand Up @@ -460,6 +460,14 @@

Custom_Location_Enable_Failed_warning = """Important! Custom Location feature wasn't enabled due to insufficient privileges on the Service Principal Name. If the custom location feature is not enabled, you will encounter an error when creating the custom location. Refer to: https://aka.ms/enable-cl-spn"""

KubeApi_Connectivity_Failed_Warning = """Unable to verify connectivity to the Kubernetes cluster.
Please check https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/diagnose-connection-issues"""

Kubeconfig_Load_Failed_Warning = """Unable to load the kubeconfig file.
Please check https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/diagnose-connection-issues#is-kubeconfig-pointing-to-the-right-cluster"""

Cluster_Already_Onboarded_Error = """The kubernetes cluster is already onboarded.
Please check if the Kubeconfig is pointing to the correct cluster using command: kubectl config current-context."""

# Diagnostic Results Name
Outbound_Connectivity_Check_Result_String = "Outbound Network Connectivity"
Expand All @@ -481,8 +489,8 @@
DEFAULT_MAX_ONBOARDING_TIMEOUT_HELMVALUE_SECONDS = "1200"

# URL constants
CLIENT_PROXY_MCR_TARGET = "mcr.microsoft.com/azureconnectivity/proxy"
HELM_MCR_URL = "mcr.microsoft.com/azurearck8s/helm"
CLIENT_PROXY_MCR_TARGET = "azureconnectivity/proxy"
HELM_MCR_URL = "azurearck8s/helm"
HELM_VERSION = "v3.12.2"
Download_And_Install_Kubectl_Fault_Type = "Failed to download and install kubectl"
Azure_Access_Token_Variable = "AZURE_ACCESS_TOKEN"
Expand Down Expand Up @@ -517,3 +525,9 @@
# "Application code shouldn't block the creation of resources for a resource provider that is in the registering state."
# See https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-providers-and-types#register-resource-provider
allowed_rp_registration_states = ["Registering", "Registered"]

GATEWAY_LINK_FAULT_TYPE = "gateway-link-error"
Gateway_Cluster_Resource_Update_Failed_Fault_Type = "Gateway-Cluster-Resource-Update-Failed"
GATEWAY_ASSOCIATE_URL = (
"https://{location}.management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.Kubernetes/connectedClusters/{cluster_name}/providers/Microsoft.HybridCompute/settings/Default?api-version={api_version}"
)
8 changes: 7 additions & 1 deletion src/connectedk8s/azext_connectedk8s/_precheckutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import azext_connectedk8s._utils as azext_utils

if TYPE_CHECKING:
from knack.commands import CLICommand
from kubernetes.client import BatchV1Api, CoreV1Api

logger = get_logger(__name__)
Expand All @@ -30,6 +31,7 @@


def fetch_diagnostic_checks_results(
cmd: CLICommand,
corev1_api_instance: CoreV1Api,
batchv1_api_instance: BatchV1Api,
helm_client_location: str,
Expand All @@ -52,6 +54,7 @@ def fetch_diagnostic_checks_results(
# Executing the cluster_diagnostic_checks job and fetching the logs obtained
cluster_diagnostic_checks_container_log = (
executing_cluster_diagnostic_checks_job(
cmd,
corev1_api_instance,
batchv1_api_instance,
helm_client_location,
Expand Down Expand Up @@ -135,6 +138,7 @@ def fetch_diagnostic_checks_results(


def executing_cluster_diagnostic_checks_job(
cmd: CLICommand,
corev1_api_instance: CoreV1Api,
batchv1_api_instance: BatchV1Api,
helm_client_location: str,
Expand Down Expand Up @@ -208,8 +212,10 @@ def executing_cluster_diagnostic_checks_job(
)
return None

mcr_url = azext_utils.get_mcr_path(cmd)

chart_path = azext_utils.get_chart_path(
consts.Cluster_Diagnostic_Checks_Job_Registry_Path,
f"{mcr_url}/{consts.Cluster_Diagnostic_Checks_Job_Registry_Path}",
kube_config,
kube_context,
helm_client_location,
Expand Down
96 changes: 92 additions & 4 deletions src/connectedk8s/azext_connectedk8s/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from kubernetes.client import CoreV1Api, V1NodeList
from requests import Response

from azext_connectedk8s.vendored_sdks.preview_2024_07_01.models import (
from azext_connectedk8s.vendored_sdks.preview_2025_08_01.models import (
ConnectedCluster,
)

Expand All @@ -58,6 +58,28 @@
# pylint: disable=bare-except


def get_mcr_path(cmd: CLICommand) -> str:
active_directory_array = cmd.cli_ctx.cloud.endpoints.active_directory.split(".")

# default for public, mc, ff clouds
mcr_postfix = active_directory_array[2]
# special cases for USSec, exclude part of suffix
if len(active_directory_array) == 4 and active_directory_array[2] == "microsoft":
mcr_postfix = active_directory_array[3]
# special case for USNat
elif len(active_directory_array) == 5:
mcr_postfix = (
active_directory_array[2]
+ "."
+ active_directory_array[3]
+ "."
+ active_directory_array[4]
)

mcr_url = f"mcr.microsoft.{mcr_postfix}"
return mcr_url


def validate_connect_rp_location(cmd: CLICommand, location: str) -> None:
subscription_id = (
os.getenv("AZURE_SUBSCRIPTION_ID")
Expand Down Expand Up @@ -801,7 +823,7 @@ def get_helm_values(
chart_location_url = f"{config_dp_endpoint}/{chart_location_url_segment}"
dp_request_identity = connected_cluster.identity
identity = connected_cluster.id
request_dict = connected_cluster.serialize()
request_dict = connected_cluster.as_dict()
request_dict["identity"]["tenantId"] = dp_request_identity.tenant_id
request_dict["identity"]["principalId"] = dp_request_identity.principal_id
request_dict["id"] = identity
Expand Down Expand Up @@ -881,6 +903,71 @@ def health_check_dp(cmd: CLICommand, config_dp_endpoint: str) -> bool:
raise CLIInternalError("Error while performing DP health check")


def update_gateway_cluster_link(
cmd: CLICommand,
location: str,
subscription_id: str,
resource_group: str,
cluster_name: str,
gateway_resource_id: str = None
) -> bool:
"""
Associates or disassociates a gateway with a cluster.

If `gateway_resource_id` is provided, performs association.
If `gateway_resource_id` is None, performs disassociation.
"""
api_version = "2025-02-19-preview"
is_association = gateway_resource_id is not None
resource = cmd.cli_ctx.cloud.endpoints.active_directory_resource_id
url = consts.GATEWAY_ASSOCIATE_URL.format(
location=location,
subscription_id=subscription_id,
resource_group=resource_group,
cluster_name=cluster_name,
api_version=api_version
)

headers = [
"Content-Type=application/json",
"Accept=application/json"
]

token = os.getenv("AZURE_ACCESS_TOKEN")
if token:
headers.append(f"Authorization=Bearer {token}")

operation_type = "association" if is_association else "disassociation"
body = {
"properties": {
"gatewayProperties": {
"gatewayResourceId": gateway_resource_id # None in case of disassociation
}
}
}
response = send_request_with_retries(
cmd.cli_ctx,
method="put",
url=url,
headers=headers,
fault_type=consts.GATEWAY_LINK_FAULT_TYPE,
summary=f"Error during gateway {operation_type}",
request_body=json.dumps(body),
resource=resource
)

if response.status_code == 200:
logger.info(f"Gateway {operation_type} succeeded for cluster '{cluster_name}' in resource group '{resource_group}'.")
return True

telemetry.set_exception(
exception=f"Gateway {operation_type} failed",
fault_type=consts.GATEWAY_LINK_FAULT_TYPE,
summary=f"Gateway {operation_type} failed"
)
raise CLIInternalError(f"Gateway {operation_type} failed for cluster '{cluster_name}'.")


def send_request_with_retries(
cli_ctx: AzCli,
method: str,
Expand Down Expand Up @@ -970,10 +1057,10 @@ def arm_exception_handler(
status_code = ex.status_code
if status_code == 404 and return_if_not_found:
return
if status_code // 100 == 4:
if status_code is not None and status_code // 100 == 4:
telemetry.set_user_fault()
telemetry.set_exception(exception=ex, fault_type=fault_type, summary=summary)
if status_code // 100 == 5:
if status_code is not None and status_code // 100 == 5:
raise AzureInternalError(
"Http response error occured while making ARM request: "
+ str(ex)
Expand Down Expand Up @@ -1332,6 +1419,7 @@ def helm_install_release(
"Please check if the azure-arc namespace was deployed and run 'kubectl get pods -n azure-arc' "
"to check if all the pods are in running state. A possible cause for pods stuck in pending "
"state could be insufficient resources on the kubernetes cluster to onboard to arc."
"Also pod logs can be checked using kubectl logs <pod-name> -n azure-arc.\n"
)
logger.warning(warn_msg)
raise CLIInternalError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@
from azure.cli.core import azclierror, telemetry
from azure.cli.core.style import Style, print_styled_text
from knack import log
from knack.commands import CLICommand

import azext_connectedk8s._constants as consts
import azext_connectedk8s._fileutils as file_utils
import azext_connectedk8s._utils as utils

logger = log.get_logger(__name__)


# Downloads client side proxy to connect to Arc Connectivity Platform
def install_client_side_proxy(
arc_proxy_folder: Optional[str], debug: bool = False
cmd: CLICommand, arc_proxy_folder: Optional[str], debug: bool = False
) -> str:
client_operating_system = _get_client_operating_system()
client_architecture = _get_client_architeture()
Expand All @@ -48,7 +50,11 @@ def install_client_side_proxy(
)

_download_proxy_from_MCR(
install_dir, proxy_name, client_operating_system, client_architecture
cmd,
install_dir,
proxy_name,
client_operating_system,
client_architecture,
)
_check_proxy_installation(install_dir, proxy_name, debug)

Expand All @@ -64,15 +70,21 @@ def install_client_side_proxy(


def _download_proxy_from_MCR(
dest_dir: str, proxy_name: str, operating_system: str, architecture: str
cmd: CLICommand,
dest_dir: str,
proxy_name: str,
operating_system: str,
architecture: str,
) -> None:
mar_target = f"{consts.CLIENT_PROXY_MCR_TARGET}/{operating_system.lower()}/{architecture}/arc-proxy"
mcr_url = utils.get_mcr_path(cmd)

mar_target = f"{mcr_url}/{consts.CLIENT_PROXY_MCR_TARGET}/{operating_system.lower()}/{architecture}/arc-proxy"
logger.debug(
"Downloading Arc Connectivity Proxy from %s in Microsoft Artifact Regristy.",
mar_target,
)

client = oras.client.OrasClient()
client = oras.client.OrasClient(hostname=mcr_url)
t0 = time.time()

try:
Expand Down
Loading
Loading