From 172bf9005a578851daf0ab1d38682bee0d160190 Mon Sep 17 00:00:00 2001 From: Sophie Zhao Date: Thu, 25 Mar 2021 14:58:00 -0400 Subject: [PATCH 01/22] add troubleshoot command under connectedk8s --- src/connectedk8s/README.md | 8 +++++++- src/connectedk8s/azext_connectedk8s/_help.py | 8 ++++++++ src/connectedk8s/azext_connectedk8s/_params.py | 4 ++++ src/connectedk8s/azext_connectedk8s/commands.py | 2 +- src/connectedk8s/azext_connectedk8s/custom.py | 4 ++++ 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/connectedk8s/README.md b/src/connectedk8s/README.md index a2a3690f524..9ffb65a86fe 100644 --- a/src/connectedk8s/README.md +++ b/src/connectedk8s/README.md @@ -51,4 +51,10 @@ or az connectedk8s delete \ --ids "/subscriptions/subscription_id/resourceGroups/my-rg/providers/Microsoft.Kubernetes/connectedClusters/my-cluster" \ -y -``` \ No newline at end of file +``` + +#### Troubleshoot and get logs for a broken cluster +``` +az connectedk8s troubleshoot \ + --resource-group my-rg \ + --name my-cluster \ \ No newline at end of file diff --git a/src/connectedk8s/azext_connectedk8s/_help.py b/src/connectedk8s/azext_connectedk8s/_help.py index 8f41bb82ed6..032cd7407fb 100644 --- a/src/connectedk8s/azext_connectedk8s/_help.py +++ b/src/connectedk8s/azext_connectedk8s/_help.py @@ -117,3 +117,11 @@ - name: Disable multiple features. text: az connectedk8s disable-features -n clusterName -g resourceGroupName --features custom-locations azure-rbac """ + +helps['connectedk8s troubleshoot'] = """ +type: command + short-summary: Troubleshoots and gets logs for a broken cluster. + examples: + - name: Troubleshoots a broken cluster . + text: az connectedk8s troubleshoot -n clusterName -g resourceGroupName +""" diff --git a/src/connectedk8s/azext_connectedk8s/_params.py b/src/connectedk8s/azext_connectedk8s/_params.py index 12e4fc53c84..e1b48cb4cf9 100644 --- a/src/connectedk8s/azext_connectedk8s/_params.py +++ b/src/connectedk8s/azext_connectedk8s/_params.py @@ -84,3 +84,7 @@ def load_arguments(self, _): c.argument('context_name', options_list=['--kube-context'], help='If specified, overwrite the default context name.') c.argument('path', options_list=['--file', '-f'], type=file_type, completer=FilesCompleter(), default=os.path.join(os.path.expanduser('~'), '.kube', 'config'), help="Kubernetes configuration file to update. If not provided, updates the file '~/.kube/config'. Use '-' to print YAML to stdout instead.") c.argument('api_server_port', options_list=['--port'], help='Port used for accessing connected cluster.') + + with self.argument_context('connectedk8s troubleshoot') as c: + c.argument('cluster_name', options_list=['--name', '-n'], id_part='name', help='The name of the connected cluster.') + c.argument('location', arg_type=get_location_type(self.cli_ctx), validator=get_default_location_from_resource_group) diff --git a/src/connectedk8s/azext_connectedk8s/commands.py b/src/connectedk8s/azext_connectedk8s/commands.py index 6c79422d655..2db739bcc64 100644 --- a/src/connectedk8s/azext_connectedk8s/commands.py +++ b/src/connectedk8s/azext_connectedk8s/commands.py @@ -28,7 +28,7 @@ def load_command_table(self, _): g.custom_command('enable-features', 'enable_features', is_preview=True) g.custom_command('disable-features', 'disable_features', is_preview=True) g.custom_command('list', 'list_connectedk8s', table_transformer=connectedk8s_list_table_format) + g.custom_command('troubleshoot', 'troubleshoot', supports_no_wait=True) g.custom_show_command('show', 'get_connectedk8s', table_transformer=connectedk8s_show_table_format) - with self.command_group('connectedk8s', connectedk8s_sdk_prev, client_factory=cf_connected_cluster_prev_2021_04_01) as g: g.custom_command('proxy', 'client_side_proxy_wrapper', is_preview=True) diff --git a/src/connectedk8s/azext_connectedk8s/custom.py b/src/connectedk8s/azext_connectedk8s/custom.py index 594c2feaad4..4f62552a224 100644 --- a/src/connectedk8s/azext_connectedk8s/custom.py +++ b/src/connectedk8s/azext_connectedk8s/custom.py @@ -2022,3 +2022,7 @@ def check_if_csp_is_running(clientproxy_process): return True else: return False + +def troubleshoot(resource_group_name, cluster_name): + print("Hello world") + return "Needs implementation" \ No newline at end of file From 29a8518c1dba4e3c54c71628be4bfea145b70f1a Mon Sep 17 00:00:00 2001 From: Sophie Zhao Date: Thu, 25 Mar 2021 17:08:11 -0400 Subject: [PATCH 02/22] resolve check issues --- src/connectedk8s/azext_connectedk8s/_help.py | 2 +- src/connectedk8s/azext_connectedk8s/_params.py | 10 +++++----- src/connectedk8s/azext_connectedk8s/custom.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/connectedk8s/azext_connectedk8s/_help.py b/src/connectedk8s/azext_connectedk8s/_help.py index 032cd7407fb..7014871841c 100644 --- a/src/connectedk8s/azext_connectedk8s/_help.py +++ b/src/connectedk8s/azext_connectedk8s/_help.py @@ -119,7 +119,7 @@ """ helps['connectedk8s troubleshoot'] = """ -type: command + type: command short-summary: Troubleshoots and gets logs for a broken cluster. examples: - name: Troubleshoots a broken cluster . diff --git a/src/connectedk8s/azext_connectedk8s/_params.py b/src/connectedk8s/azext_connectedk8s/_params.py index e1b48cb4cf9..89e5b9dc0f6 100644 --- a/src/connectedk8s/azext_connectedk8s/_params.py +++ b/src/connectedk8s/azext_connectedk8s/_params.py @@ -78,13 +78,13 @@ def load_arguments(self, _): c.argument('kube_config', options_list=['--kube-config'], help='Path to the kube config file.') c.argument('kube_context', options_list=['--kube-context'], help='Kubconfig context from current machine.') + with self.argument_context('connectedk8s troubleshoot') as c: + c.argument('cluster_name', options_list=['--name', '-n'], id_part='name', help='The name of the connected cluster.') + with self.argument_context('connectedk8s proxy') as c: c.argument('cluster_name', options_list=['--name', '-n'], id_part='name', help='The name of the connected cluster.') c.argument('token', options_list=['--token'], help='Service account token to use to authenticate to the kubernetes cluster.') c.argument('context_name', options_list=['--kube-context'], help='If specified, overwrite the default context name.') c.argument('path', options_list=['--file', '-f'], type=file_type, completer=FilesCompleter(), default=os.path.join(os.path.expanduser('~'), '.kube', 'config'), help="Kubernetes configuration file to update. If not provided, updates the file '~/.kube/config'. Use '-' to print YAML to stdout instead.") - c.argument('api_server_port', options_list=['--port'], help='Port used for accessing connected cluster.') - - with self.argument_context('connectedk8s troubleshoot') as c: - c.argument('cluster_name', options_list=['--name', '-n'], id_part='name', help='The name of the connected cluster.') - c.argument('location', arg_type=get_location_type(self.cli_ctx), validator=get_default_location_from_resource_group) + c.argument('api_server_port', options_list=['--port'], help='Port used for accessing connected cluster.') + \ No newline at end of file diff --git a/src/connectedk8s/azext_connectedk8s/custom.py b/src/connectedk8s/azext_connectedk8s/custom.py index 4f62552a224..0537d92dac2 100644 --- a/src/connectedk8s/azext_connectedk8s/custom.py +++ b/src/connectedk8s/azext_connectedk8s/custom.py @@ -2023,6 +2023,6 @@ def check_if_csp_is_running(clientproxy_process): else: return False + def troubleshoot(resource_group_name, cluster_name): - print("Hello world") return "Needs implementation" \ No newline at end of file From fe3c1e8566cd2831908be38078c68880f3454dd2 Mon Sep 17 00:00:00 2001 From: Sophie Zhao Date: Thu, 25 Mar 2021 17:33:25 -0400 Subject: [PATCH 03/22] fix final new line --- src/connectedk8s/azext_connectedk8s/_params.py | 6 +----- src/connectedk8s/azext_connectedk8s/custom.py | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/connectedk8s/azext_connectedk8s/_params.py b/src/connectedk8s/azext_connectedk8s/_params.py index 89e5b9dc0f6..72ad36bba89 100644 --- a/src/connectedk8s/azext_connectedk8s/_params.py +++ b/src/connectedk8s/azext_connectedk8s/_params.py @@ -78,13 +78,9 @@ def load_arguments(self, _): c.argument('kube_config', options_list=['--kube-config'], help='Path to the kube config file.') c.argument('kube_context', options_list=['--kube-context'], help='Kubconfig context from current machine.') - with self.argument_context('connectedk8s troubleshoot') as c: - c.argument('cluster_name', options_list=['--name', '-n'], id_part='name', help='The name of the connected cluster.') - with self.argument_context('connectedk8s proxy') as c: c.argument('cluster_name', options_list=['--name', '-n'], id_part='name', help='The name of the connected cluster.') c.argument('token', options_list=['--token'], help='Service account token to use to authenticate to the kubernetes cluster.') c.argument('context_name', options_list=['--kube-context'], help='If specified, overwrite the default context name.') c.argument('path', options_list=['--file', '-f'], type=file_type, completer=FilesCompleter(), default=os.path.join(os.path.expanduser('~'), '.kube', 'config'), help="Kubernetes configuration file to update. If not provided, updates the file '~/.kube/config'. Use '-' to print YAML to stdout instead.") - c.argument('api_server_port', options_list=['--port'], help='Port used for accessing connected cluster.') - \ No newline at end of file + c.argument('api_server_port', options_list=['--port'], help='Port used for accessing connected cluster.') diff --git a/src/connectedk8s/azext_connectedk8s/custom.py b/src/connectedk8s/azext_connectedk8s/custom.py index 0537d92dac2..184f4881958 100644 --- a/src/connectedk8s/azext_connectedk8s/custom.py +++ b/src/connectedk8s/azext_connectedk8s/custom.py @@ -2025,4 +2025,4 @@ def check_if_csp_is_running(clientproxy_process): def troubleshoot(resource_group_name, cluster_name): - return "Needs implementation" \ No newline at end of file + return "Needs implementation" From be0e8cdf571cab543a75948dc58aeef6d033bd54 Mon Sep 17 00:00:00 2001 From: Sophie Zhao Date: Thu, 25 Mar 2021 17:53:38 -0400 Subject: [PATCH 04/22] add back troubleshoot params --- src/connectedk8s/azext_connectedk8s/_params.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/connectedk8s/azext_connectedk8s/_params.py b/src/connectedk8s/azext_connectedk8s/_params.py index 72ad36bba89..ddca87212b8 100644 --- a/src/connectedk8s/azext_connectedk8s/_params.py +++ b/src/connectedk8s/azext_connectedk8s/_params.py @@ -83,4 +83,7 @@ def load_arguments(self, _): c.argument('token', options_list=['--token'], help='Service account token to use to authenticate to the kubernetes cluster.') c.argument('context_name', options_list=['--kube-context'], help='If specified, overwrite the default context name.') c.argument('path', options_list=['--file', '-f'], type=file_type, completer=FilesCompleter(), default=os.path.join(os.path.expanduser('~'), '.kube', 'config'), help="Kubernetes configuration file to update. If not provided, updates the file '~/.kube/config'. Use '-' to print YAML to stdout instead.") - c.argument('api_server_port', options_list=['--port'], help='Port used for accessing connected cluster.') + c.argument('api_server_port', options_list=['--port'], help='Port used for accessing connected cluster.') + + with self.argument_context('connectedk8s troubleshoot') as c: + c.argument('cluster_name', options_list=['--name', '-n'], id_part='name', help='The name of the connected cluster.') From 18059a8783aed7ea0d9a3c97d734ebd049ff3e4d Mon Sep 17 00:00:00 2001 From: Sophie Zhao Date: Mon, 5 Apr 2021 11:21:26 -0400 Subject: [PATCH 05/22] implement helm version and permission checks --- src/connectedk8s/MANIFEST.in | 1 + .../azext_connectedk8s/_constants.py | 1 + src/connectedk8s/azext_connectedk8s/_help.py | 6 +- src/connectedk8s/azext_connectedk8s/custom.py | 70 ++++++++++++++++++- .../azext_connectedk8s/troubleshoot.json | 26 +++++++ src/connectedk8s/setup.py | 4 +- 6 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 src/connectedk8s/MANIFEST.in create mode 100644 src/connectedk8s/azext_connectedk8s/troubleshoot.json diff --git a/src/connectedk8s/MANIFEST.in b/src/connectedk8s/MANIFEST.in new file mode 100644 index 00000000000..3eafd0662e5 --- /dev/null +++ b/src/connectedk8s/MANIFEST.in @@ -0,0 +1 @@ +include azext_connectedk8s/troubleshoot.json \ No newline at end of file diff --git a/src/connectedk8s/azext_connectedk8s/_constants.py b/src/connectedk8s/azext_connectedk8s/_constants.py index 7d90c72732a..c7b015b2e2c 100644 --- a/src/connectedk8s/azext_connectedk8s/_constants.py +++ b/src/connectedk8s/azext_connectedk8s/_constants.py @@ -41,6 +41,7 @@ Kubernetes_Connectivity_FaultType = 'kubernetes-cluster-connection-error' Helm_Version_Fault_Type = 'helm-not-updated-error' Check_HelmVersion_Fault_Type = 'helm-version-check-error' +Check_AzureCliVersion_Fault_Type = 'azure-version-check-error' Helm_Installation_Fault_Type = 'helm-not-installed-error' Check_HelmInstallation_Fault_Type = 'check-helm-installed-error' Get_HelmRegistery_Path_Fault_Type = 'helm-registry-path-fetch-error' diff --git a/src/connectedk8s/azext_connectedk8s/_help.py b/src/connectedk8s/azext_connectedk8s/_help.py index 7014871841c..d4e74b7c976 100644 --- a/src/connectedk8s/azext_connectedk8s/_help.py +++ b/src/connectedk8s/azext_connectedk8s/_help.py @@ -120,8 +120,8 @@ helps['connectedk8s troubleshoot'] = """ type: command - short-summary: Troubleshoots and gets logs for a broken cluster. + short-summary: Collects diagnose infomation and gets logs on the connected cluster. examples: - - name: Troubleshoots a broken cluster . - text: az connectedk8s troubleshoot -n clusterName -g resourceGroupName + - name: Troubleshoots a connected cluster . + text: az connectedk8s troubleshoot -g resourceGroupName -n connectedClusterName """ diff --git a/src/connectedk8s/azext_connectedk8s/custom.py b/src/connectedk8s/azext_connectedk8s/custom.py index 184f4881958..162f392aad8 100644 --- a/src/connectedk8s/azext_connectedk8s/custom.py +++ b/src/connectedk8s/azext_connectedk8s/custom.py @@ -44,6 +44,7 @@ from .vendored_sdks.models import ConnectedCluster, ConnectedClusterIdentity from threading import Timer, Thread import sys +from packaging import version logger = get_logger(__name__) # pylint:disable=unused-argument # pylint: disable=too-many-locals @@ -2023,6 +2024,73 @@ def check_if_csp_is_running(clientproxy_process): else: return False +def check_helm_installed(condition, failMsg, passMsg, username): + kube_config = set_kube_config(None) + + # Loading the kubeconfig file in kubernetes client configuration + load_kube_config(kube_config, None) + configuration = kube_client.Configuration() + + # Checking the connection to kubernetes cluster. + # This check was added to avoid large timeouts when connecting to AAD Enabled + # AKS clusters if the user had not logged in. + check_kube_connection(configuration) + + # Checking helm installation + try: + check_helm_install(kube_config, None) + print(passMsg) + except Exception as e: + print(e) + print(failMsg) + return + + +def check_helm_3(condition, failMsg, passMsg, username): + try: + ver = check_helm_version(None, None)[1:] + if eval("version.parse(ver)"+condition): + print(passMsg) + else: + print(failMsg) + return + except Exception as e: + print(e) + print(failMsg) + return + + + +def check_azure_folder_permissions(condition, failMsg, passMsg, username): + #username = input("Checking to see if you have access to ~/.azure. Please enter your username: ") + try: + path = 'C:\\Users\\'+username+'\\.azure' + for dirpath, dirnames, filenames in os.walk(path): + dirs = [os.path.join(dirpath, x) for x in dirnames] + for dirname in dirs: + if os.access(path, os.R_OK | os.X_OK | os.W_OK): + print(path + " "+passMsg) + else: + cmd = "(Get-Acl "+path+").Access | ?{$_.IdentityReference -match \""+username+"\"} | Select IdentityReference,FileSystemRights" + #permissions = run(["powershell", "-Command", cmd], capture_output=True) + print(failMsg + cmd) + except Exception as e: + print(e) + print("An exception occured: Please try again.") + return def troubleshoot(resource_group_name, cluster_name): - return "Needs implementation" + username = input("Please enter your username.This is used to locate the ~/.azure folder on windows: ") + troubleshoot_file= 'C:\\Users\\'+username+'\\.azure\\cliextensions\\connectedk8s\\azext_connectedk8s\\troubleshoot.json' + platform = '' + try: + with open(troubleshoot_file, 'r') as f: + checks = json.load(f) + for check in checks: + if check['Enabled']: + method = eval(check["Method"]+'(check["Condition"], check["FailMessage"], check["PassMessage"], username)') + method + except OSError as e: + print(e) + print("Please ensure troubleshoot.json is in local directory.") + diff --git a/src/connectedk8s/azext_connectedk8s/troubleshoot.json b/src/connectedk8s/azext_connectedk8s/troubleshoot.json new file mode 100644 index 00000000000..3d84155a15f --- /dev/null +++ b/src/connectedk8s/azext_connectedk8s/troubleshoot.json @@ -0,0 +1,26 @@ +[ + { + "Check": "HelmInstallation", + "Condition": "true", + "Method": "check_helm_installed", + "PassMessage": "Helm installation check passed!", + "FailMessage": "Helm not found. Please install the latest version of Helm. Learn more at https://aka.ms/arc/k8s/onboarding-helm-install", + "Enabled": "true" + }, + { + "Check": "HelmVersion", + "Condition": ">=version.parse(\"3.0\")", + "Method": "check_helm_3", + "PassMessage": "Your Helm version is up to date!", + "FailMessage": "Helm 3+ required. Please visit https://aka.ms/arc/k8s/onboarding-helm-install to download the latest installation of Helm.", + "Enabled": "true" + }, + { + "Check": "AzurePermissionCheck", + "Condition": "true", + "Method": "check_azure_folder_permissions", + "PassMessage": "You have the correct permissions!", + "FailMessage": "Please use the following command to get the correct permissions: ", + "Enabled": "true" + } +] \ No newline at end of file diff --git a/src/connectedk8s/setup.py b/src/connectedk8s/setup.py index 70ab8aa0d54..4bf4cab22a5 100644 --- a/src/connectedk8s/setup.py +++ b/src/connectedk8s/setup.py @@ -58,5 +58,7 @@ classifiers=CLASSIFIERS, packages=find_packages(), install_requires=DEPENDENCIES, - package_data={'azext_connectedk8s': ['azext_metadata.json']}, + package_data={'azext_connectedk8s': ['azext_metadata.json', 'troubleshoot.json']}, + #data_files=[('azext_connectedk8s', ['troubleshoot.json'])], + include_package_data=True ) From ffa60f7d028e364e9b1af58670d22b5b258ea26c Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 00:41:03 -0700 Subject: [PATCH 06/22] add constants --- src/aks-preview/azext_aks_preview/_consts.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/aks-preview/azext_aks_preview/_consts.py b/src/aks-preview/azext_aks_preview/_consts.py index ab9a16d2e78..05a1bb94dc9 100644 --- a/src/aks-preview/azext_aks_preview/_consts.py +++ b/src/aks-preview/azext_aks_preview/_consts.py @@ -332,3 +332,11 @@ # GPU Driver Type Consts CONST_GPU_DRIVER_TYPE_CUDA = "CUDA" CONST_GPU_DRIVER_TYPE_GRID = "GRID" + +# aks extension constants +CONST_K8S_EXTENSION_CUSTOM_MOD_NAME = "azext_k8s_extension.custom" +CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME = "azext_k8s_extension._client_factory" +CONST_K8S_EXTENSION_TYPE_CLIENT_FACTORY_MOD_NAME = "azext_k8s_extension_types._client_factory" +CONST_K8S_EXTENSION_NAME = "k8s-extension" +CONST_K8S_EXTENSION_ACTION_MOD_NAME = "azext_k8s_extension.action" +CONST_K8S_EXTENSION_FORMAT_MOD_NAME = "azext_k8s_extension._format" \ No newline at end of file From c09ed468097df13b453cc14cd5694dd1350ffd25 Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 08:27:17 -0700 Subject: [PATCH 07/22] add aks extension cmd group --- src/aks-preview/azext_aks_preview/_consts.py | 2 +- src/aks-preview/azext_aks_preview/_help.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/aks-preview/azext_aks_preview/_consts.py b/src/aks-preview/azext_aks_preview/_consts.py index 05a1bb94dc9..351a016a31a 100644 --- a/src/aks-preview/azext_aks_preview/_consts.py +++ b/src/aks-preview/azext_aks_preview/_consts.py @@ -339,4 +339,4 @@ CONST_K8S_EXTENSION_TYPE_CLIENT_FACTORY_MOD_NAME = "azext_k8s_extension_types._client_factory" CONST_K8S_EXTENSION_NAME = "k8s-extension" CONST_K8S_EXTENSION_ACTION_MOD_NAME = "azext_k8s_extension.action" -CONST_K8S_EXTENSION_FORMAT_MOD_NAME = "azext_k8s_extension._format" \ No newline at end of file +CONST_K8S_EXTENSION_FORMAT_MOD_NAME = "azext_k8s_extension._format" diff --git a/src/aks-preview/azext_aks_preview/_help.py b/src/aks-preview/azext_aks_preview/_help.py index e08fe641ba5..e86c15f2e02 100644 --- a/src/aks-preview/azext_aks_preview/_help.py +++ b/src/aks-preview/azext_aks_preview/_help.py @@ -3300,3 +3300,8 @@ type: string short-summary: Additional endpoint(s) to perform the connectivity check, separated by comma. """ + +helps['aks extension'] = """ + type: group + short-summary: Commands to manage extensions in Kubernetes cluster +""" From f714778e4632412870d7516fe7ff866b72871b4c Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 08:39:31 -0700 Subject: [PATCH 08/22] revert connectedk8s changes --- src/connectedk8s/README.md | 6 ----- .../azext_connectedk8s/troubleshoot.json | 26 ------------------- 2 files changed, 32 deletions(-) delete mode 100644 src/connectedk8s/azext_connectedk8s/troubleshoot.json diff --git a/src/connectedk8s/README.md b/src/connectedk8s/README.md index 9ffb65a86fe..24868b1a5e7 100644 --- a/src/connectedk8s/README.md +++ b/src/connectedk8s/README.md @@ -52,9 +52,3 @@ az connectedk8s delete \ --ids "/subscriptions/subscription_id/resourceGroups/my-rg/providers/Microsoft.Kubernetes/connectedClusters/my-cluster" \ -y ``` - -#### Troubleshoot and get logs for a broken cluster -``` -az connectedk8s troubleshoot \ - --resource-group my-rg \ - --name my-cluster \ \ No newline at end of file diff --git a/src/connectedk8s/azext_connectedk8s/troubleshoot.json b/src/connectedk8s/azext_connectedk8s/troubleshoot.json deleted file mode 100644 index 3d84155a15f..00000000000 --- a/src/connectedk8s/azext_connectedk8s/troubleshoot.json +++ /dev/null @@ -1,26 +0,0 @@ -[ - { - "Check": "HelmInstallation", - "Condition": "true", - "Method": "check_helm_installed", - "PassMessage": "Helm installation check passed!", - "FailMessage": "Helm not found. Please install the latest version of Helm. Learn more at https://aka.ms/arc/k8s/onboarding-helm-install", - "Enabled": "true" - }, - { - "Check": "HelmVersion", - "Condition": ">=version.parse(\"3.0\")", - "Method": "check_helm_3", - "PassMessage": "Your Helm version is up to date!", - "FailMessage": "Helm 3+ required. Please visit https://aka.ms/arc/k8s/onboarding-helm-install to download the latest installation of Helm.", - "Enabled": "true" - }, - { - "Check": "AzurePermissionCheck", - "Condition": "true", - "Method": "check_azure_folder_permissions", - "PassMessage": "You have the correct permissions!", - "FailMessage": "Please use the following command to get the correct permissions: ", - "Enabled": "true" - } -] \ No newline at end of file From d6029cb317334c0ba90baf8d2ac64983b963d1c5 Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 08:40:34 -0700 Subject: [PATCH 09/22] update readme --- src/connectedk8s/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/connectedk8s/README.md b/src/connectedk8s/README.md index 24868b1a5e7..a2a3690f524 100644 --- a/src/connectedk8s/README.md +++ b/src/connectedk8s/README.md @@ -51,4 +51,4 @@ or az connectedk8s delete \ --ids "/subscriptions/subscription_id/resourceGroups/my-rg/providers/Microsoft.Kubernetes/connectedClusters/my-cluster" \ -y -``` +``` \ No newline at end of file From da8cdf6ad27077ca0ef0ef0a3c961e0b69003af0 Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 09:06:17 -0700 Subject: [PATCH 10/22] add helper functions --- src/aks-preview/azext_aks_preview/_help.py | 5 ---- src/aks-preview/azext_aks_preview/_helpers.py | 25 ++++++++++++++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/aks-preview/azext_aks_preview/_help.py b/src/aks-preview/azext_aks_preview/_help.py index e86c15f2e02..e08fe641ba5 100644 --- a/src/aks-preview/azext_aks_preview/_help.py +++ b/src/aks-preview/azext_aks_preview/_help.py @@ -3300,8 +3300,3 @@ type: string short-summary: Additional endpoint(s) to perform the connectivity check, separated by comma. """ - -helps['aks extension'] = """ - type: group - short-summary: Commands to manage extensions in Kubernetes cluster -""" diff --git a/src/aks-preview/azext_aks_preview/_helpers.py b/src/aks-preview/azext_aks_preview/_helpers.py index e952f238318..da70b65e139 100644 --- a/src/aks-preview/azext_aks_preview/_helpers.py +++ b/src/aks-preview/azext_aks_preview/_helpers.py @@ -19,7 +19,8 @@ from azext_aks_preview._consts import ( ADDONS, - CONST_MONITORING_ADDON_NAME + CONST_MONITORING_ADDON_NAME, + CONST_K8S_EXTENSION_NAME, ) from azure.cli.command_modules.acs._helpers import map_azure_error_to_cli_error @@ -28,6 +29,7 @@ FileOperationError, InvalidArgumentValueError, ResourceNotFoundError, + UnknownError, ) from azure.core.exceptions import AzureError from knack.log import get_logger @@ -395,3 +397,24 @@ def filter_hard_taints(node_initialization_taints: List[str]) -> List[str]: # If the taint doesn't have a recognizable format, keep it, if it's incorrect - AKS-RP will return an error filtered_taints.append(taint) return filtered_taints + + +def get_k8s_extension_module(module_name): + try: + # adding the installed extension in the path + from azure.cli.core.extension.operations import add_extension_to_path + add_extension_to_path(CONST_K8S_EXTENSION_NAME) + # import the extension module + from importlib import import_module + azext_custom = import_module(module_name) + return azext_custom + except ImportError: + raise UnknownError( # pylint: disable=raise-missing-from + "Please add CLI extension `k8s-extension` for performing Azure Container Storage operations.\n" + "Run command `az extension add --name k8s-extension`" + ) + + +def check_if_extension_type_is_in_allow_list(extension_type_name): + allowedListOfExtensions = ["microsoft.dataprotection.kubernetes", "microsoft.flux"] + return extension_type_name.lower() in allowedListOfExtensions From 7371f6572943b58c4f18454d6af6d3a1a5c146ee Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 09:38:10 -0700 Subject: [PATCH 11/22] add create command --- src/aks-preview/azext_aks_preview/_help.py | 35 +++++++++++ src/aks-preview/azext_aks_preview/_params.py | 63 +++++++++++++++++++ src/aks-preview/azext_aks_preview/commands.py | 4 ++ src/aks-preview/azext_aks_preview/custom.py | 59 ++++++++++++++++- 4 files changed, 160 insertions(+), 1 deletion(-) diff --git a/src/aks-preview/azext_aks_preview/_help.py b/src/aks-preview/azext_aks_preview/_help.py index e08fe641ba5..2078e162b90 100644 --- a/src/aks-preview/azext_aks_preview/_help.py +++ b/src/aks-preview/azext_aks_preview/_help.py @@ -3300,3 +3300,38 @@ type: string short-summary: Additional endpoint(s) to perform the connectivity check, separated by comma. """ + +helps['aks extension'] = """ + type: group + short-summary: Commands to manage extensions in Kubernetes cluster +""" + +helps['aks extension create'] = """ + type: command + short-summary: Creates the Kubernetes extension instance on the managed cluster. Please refer to the example at the end to see how to create a k8s extension + long-summary: Create a Kubernetes Extension. \ +The output includes secrets that you must protect. Be sure that you do not include these secrets in your \ + source control. Also verify that no secrets are present in the logs of your command or script. \ + For additional information, see http://aka.ms/clisecrets. + parameters: + - name: --extension-type -t + type: string + short-summary: Name of the extension type + - name: --cluster-name -c + type: string + short-summary: Name of the AKS cluster + - name: --name -n + type: string + short-summary: Name of the extension instance + - name: --scope + type: string + short-summary: specify scope of the extension type, takes in name or cluster as the scope + - name: --release-train + type: string + short-summary: specify the release train for the extension type + examples: + - name: Install K8s extension on AKS cluster + text: az aks extension create --resource-group my-resource-group \ +--cluster-name mycluster --name myextension --extension-type microsoft.openservicemesh \ +--scope cluster --release-train stable +""" diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index c6f1ed9efae..a1cae54b9b3 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -33,6 +33,7 @@ tags_type, zones_type, ) +from azure.cli.core.commands.validators import get_default_location_from_resource_group from azext_aks_preview._client_factory import CUSTOM_MGMT_AKS_PREVIEW from azext_aks_preview._completers import ( get_k8s_upgrades_completion_list, @@ -130,6 +131,10 @@ CONST_APP_ROUTING_NONE_NGINX, CONST_GPU_DRIVER_TYPE_CUDA, CONST_GPU_DRIVER_TYPE_GRID, + CONST_K8S_EXTENSION_ACTION_MOD_NAME, +) +from azext_aks_preview._helpers import ( + get_k8s_extension_module, ) from azext_aks_preview._validators import ( validate_acr, @@ -432,6 +437,8 @@ def load_arguments(self, _): operation_group="managed_clusters", ) + k8s_extension_action_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_ACTION_MOD_NAME) + # AKS command argument configuration with self.argument_context("aks") as c: c.argument( @@ -2325,6 +2332,62 @@ def load_arguments(self, _): help='Space-separated additional endpoint(s) to perform the connectivity check.', validator=validate_custom_endpoints) + with self.argument_context('aks extension') as c: + c.argument('location', + validator=get_default_location_from_resource_group) + c.argument('name', + options_list=['--name', '-n'], + help='Name of the extension instance') + c.argument('extension_type', + options_list=['--extension-type', '-t'], + help='Name of the extension type.') + c.argument('cluster_name', + options_list=['--cluster-name', '-c'], + help='Name of the Kubernetes cluster') + c.argument('scope', + arg_type=get_enum_type(['cluster', 'namespace']), + help='Specify the extension scope.') + c.argument('auto_upgrade_minor_version', + arg_group="Version", + options_list=['--auto-upgrade-minor-version', '--auto-upgrade'], + arg_type=get_three_state_flag(), + help='Automatically upgrade minor version of the extension instance.') + c.argument('version', + arg_group="Version", + help='Specify the version to install for the extension instance if' + ' --auto-upgrade-minor-version is not enabled.') + c.argument('release_train', + arg_group="Version", + help='Specify the release train for the extension type.') + c.argument('configuration_settings', + arg_group="Configuration", + options_list=['--configuration-settings', '--config'], + action=k8s_extension_action_mod.AddConfigurationSettings, + nargs='+', + help='Configuration Settings as key=value pair.' + + 'Repeat parameter for each setting.' + + 'Do not use this for secrets, as this value is returned in response.') + c.argument('configuration_protected_settings', + arg_group="Configuration", + options_list=['--config-protected-settings', '--config-protected'], + action=k8s_extension_action_mod.AddConfigurationProtectedSettings, + nargs='+', + help='Configuration Protected Settings as key=value pair.' + + 'Repeat parameter for each setting. Only the key is returned in response, the value is not.') + c.argument('configuration_settings_file', + arg_group="Configuration", + options_list=['--config-settings-file', '--config-file'], + help='JSON file path for configuration-settings') + c.argument('configuration_protected_settings_file', + arg_group="Configuration", + options_list=['--config-protected-settings-file', '--config-protected-file'], + help='JSON file path for configuration-protected-settings') + c.argument('release_namespace', + help='Specify the namespace to install the extension release.') + c.argument('target_namespace', + help='Specify the target namespace to install to for the extension instance. This' + ' parameter is required if extension scope is set to \'namespace\'') + def _get_default_install_location(exe_name): system = platform.system() diff --git a/src/aks-preview/azext_aks_preview/commands.py b/src/aks-preview/azext_aks_preview/commands.py index 183738999f6..f716ff8e581 100644 --- a/src/aks-preview/azext_aks_preview/commands.py +++ b/src/aks-preview/azext_aks_preview/commands.py @@ -453,3 +453,7 @@ def load_command_table(self, _): "aks check-network", managed_clusters_sdk, client_factory=cf_managed_clusters ) as g: g.custom_command("outbound", "aks_check_network_outbound") + + with self.command_group("aks extension", managed_clusters_sdk, client_factory=cf_managed_clusters) \ + as g: + g.custom_command('create', 'create_k8s_extension', supports_no_wait=True) diff --git a/src/aks-preview/azext_aks_preview/custom.py b/src/aks-preview/azext_aks_preview/custom.py index eafe732c511..600be5eed16 100644 --- a/src/aks-preview/azext_aks_preview/custom.py +++ b/src/aks-preview/azext_aks_preview/custom.py @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -# pylint: disable=too-many-lines +# pylint: disable=too-many-lines, disable=broad-except import datetime import json import os @@ -57,14 +57,18 @@ CONST_AVAILABILITY_SET, CONST_MIN_NODE_IMAGE_VERSION, CONST_ARTIFACT_SOURCE_DIRECT, + CONST_K8S_EXTENSION_CUSTOM_MOD_NAME, + CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME, ) from azext_aks_preview._helpers import ( check_is_private_link_cluster, get_cluster_snapshot_by_snapshot_id, + get_k8s_extension_module, get_nodepool_snapshot_by_snapshot_id, print_or_merge_credentials, process_message_for_run_command, check_is_monitoring_addon_enabled, + check_if_extension_type_is_in_allow_list, ) from azext_aks_preview._podidentity import ( _ensure_managed_identity_operator_permission, @@ -3704,3 +3708,56 @@ def aks_check_network_outbound( instance_id, vm_name, custom_endpoints) + + +def create_k8s_extension( + cmd, + client, + resource_group_name, + cluster_name, + name, + extension_type, + scope=None, + auto_upgrade_minor_version=None, + release_train=None, + version=None, + target_namespace=None, + release_namespace=None, + configuration_settings=None, + configuration_protected_settings=None, + configuration_settings_file=None, + configuration_protected_settings_file=None, + no_wait=False, +): + if not check_if_extension_type_is_in_allow_list(extension_type.lower()): + raise ValidationError(f"Failed to install {extension_type.lower()}" + + "as it is not in allowed list of extension types") + + k8s_extension_custom_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_CUSTOM_MOD_NAME) + client_factory = get_k8s_extension_module(CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME) + client = client_factory.cf_k8s_extension_operation(cmd.cli_ctx) + + try: + result = k8s_extension_custom_mod.create_k8s_extension( + cmd, + client, + resource_group_name, + cluster_name, + name=name, + cluster_type="managedClusters", + extension_type=extension_type, + auto_upgrade_minor_version=auto_upgrade_minor_version, + release_train=release_train, + scope=scope, + version=version, + target_namespace=target_namespace, + release_namespace=release_namespace, + configuration_settings=configuration_settings, + configuration_protected_settings=configuration_protected_settings, + configuration_settings_file=configuration_settings_file, + configuration_protected_settings_file=configuration_protected_settings_file, + no_wait=no_wait, + ) + return result + except Exception as ex: + logger.error("K8s extension failed to install.\nError: %s", ex) From bb46b398deca71b002a7b746efabf3377fb72480 Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 09:46:55 -0700 Subject: [PATCH 12/22] revert commands and custom.py --- src/aks-preview/azext_aks_preview/commands.py | 4 -- src/aks-preview/azext_aks_preview/custom.py | 59 +------------------ 2 files changed, 1 insertion(+), 62 deletions(-) diff --git a/src/aks-preview/azext_aks_preview/commands.py b/src/aks-preview/azext_aks_preview/commands.py index f716ff8e581..183738999f6 100644 --- a/src/aks-preview/azext_aks_preview/commands.py +++ b/src/aks-preview/azext_aks_preview/commands.py @@ -453,7 +453,3 @@ def load_command_table(self, _): "aks check-network", managed_clusters_sdk, client_factory=cf_managed_clusters ) as g: g.custom_command("outbound", "aks_check_network_outbound") - - with self.command_group("aks extension", managed_clusters_sdk, client_factory=cf_managed_clusters) \ - as g: - g.custom_command('create', 'create_k8s_extension', supports_no_wait=True) diff --git a/src/aks-preview/azext_aks_preview/custom.py b/src/aks-preview/azext_aks_preview/custom.py index 600be5eed16..eafe732c511 100644 --- a/src/aks-preview/azext_aks_preview/custom.py +++ b/src/aks-preview/azext_aks_preview/custom.py @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -# pylint: disable=too-many-lines, disable=broad-except +# pylint: disable=too-many-lines import datetime import json import os @@ -57,18 +57,14 @@ CONST_AVAILABILITY_SET, CONST_MIN_NODE_IMAGE_VERSION, CONST_ARTIFACT_SOURCE_DIRECT, - CONST_K8S_EXTENSION_CUSTOM_MOD_NAME, - CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME, ) from azext_aks_preview._helpers import ( check_is_private_link_cluster, get_cluster_snapshot_by_snapshot_id, - get_k8s_extension_module, get_nodepool_snapshot_by_snapshot_id, print_or_merge_credentials, process_message_for_run_command, check_is_monitoring_addon_enabled, - check_if_extension_type_is_in_allow_list, ) from azext_aks_preview._podidentity import ( _ensure_managed_identity_operator_permission, @@ -3708,56 +3704,3 @@ def aks_check_network_outbound( instance_id, vm_name, custom_endpoints) - - -def create_k8s_extension( - cmd, - client, - resource_group_name, - cluster_name, - name, - extension_type, - scope=None, - auto_upgrade_minor_version=None, - release_train=None, - version=None, - target_namespace=None, - release_namespace=None, - configuration_settings=None, - configuration_protected_settings=None, - configuration_settings_file=None, - configuration_protected_settings_file=None, - no_wait=False, -): - if not check_if_extension_type_is_in_allow_list(extension_type.lower()): - raise ValidationError(f"Failed to install {extension_type.lower()}" + - "as it is not in allowed list of extension types") - - k8s_extension_custom_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_CUSTOM_MOD_NAME) - client_factory = get_k8s_extension_module(CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME) - client = client_factory.cf_k8s_extension_operation(cmd.cli_ctx) - - try: - result = k8s_extension_custom_mod.create_k8s_extension( - cmd, - client, - resource_group_name, - cluster_name, - name=name, - cluster_type="managedClusters", - extension_type=extension_type, - auto_upgrade_minor_version=auto_upgrade_minor_version, - release_train=release_train, - scope=scope, - version=version, - target_namespace=target_namespace, - release_namespace=release_namespace, - configuration_settings=configuration_settings, - configuration_protected_settings=configuration_protected_settings, - configuration_settings_file=configuration_settings_file, - configuration_protected_settings_file=configuration_protected_settings_file, - no_wait=no_wait, - ) - return result - except Exception as ex: - logger.error("K8s extension failed to install.\nError: %s", ex) From 822bde5a023245571bbdff3e73391a886630f402 Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 09:49:11 -0700 Subject: [PATCH 13/22] update helpers.py --- src/aks-preview/azext_aks_preview/_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aks-preview/azext_aks_preview/_helpers.py b/src/aks-preview/azext_aks_preview/_helpers.py index da70b65e139..2880c6ae4c9 100644 --- a/src/aks-preview/azext_aks_preview/_helpers.py +++ b/src/aks-preview/azext_aks_preview/_helpers.py @@ -410,7 +410,7 @@ def get_k8s_extension_module(module_name): return azext_custom except ImportError: raise UnknownError( # pylint: disable=raise-missing-from - "Please add CLI extension `k8s-extension` for performing Azure Container Storage operations.\n" + "Please add CLI extension `k8s-extension` for performing K8s extension operations.\n" "Run command `az extension add --name k8s-extension`" ) From 9db388ce14b23503c922dc5e4d556689335543b4 Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 09:56:30 -0700 Subject: [PATCH 14/22] revert create changes --- src/aks-preview/azext_aks_preview/_help.py | 35 ----------- src/aks-preview/azext_aks_preview/_params.py | 62 +------------------- 2 files changed, 1 insertion(+), 96 deletions(-) diff --git a/src/aks-preview/azext_aks_preview/_help.py b/src/aks-preview/azext_aks_preview/_help.py index 2078e162b90..e08fe641ba5 100644 --- a/src/aks-preview/azext_aks_preview/_help.py +++ b/src/aks-preview/azext_aks_preview/_help.py @@ -3300,38 +3300,3 @@ type: string short-summary: Additional endpoint(s) to perform the connectivity check, separated by comma. """ - -helps['aks extension'] = """ - type: group - short-summary: Commands to manage extensions in Kubernetes cluster -""" - -helps['aks extension create'] = """ - type: command - short-summary: Creates the Kubernetes extension instance on the managed cluster. Please refer to the example at the end to see how to create a k8s extension - long-summary: Create a Kubernetes Extension. \ -The output includes secrets that you must protect. Be sure that you do not include these secrets in your \ - source control. Also verify that no secrets are present in the logs of your command or script. \ - For additional information, see http://aka.ms/clisecrets. - parameters: - - name: --extension-type -t - type: string - short-summary: Name of the extension type - - name: --cluster-name -c - type: string - short-summary: Name of the AKS cluster - - name: --name -n - type: string - short-summary: Name of the extension instance - - name: --scope - type: string - short-summary: specify scope of the extension type, takes in name or cluster as the scope - - name: --release-train - type: string - short-summary: specify the release train for the extension type - examples: - - name: Install K8s extension on AKS cluster - text: az aks extension create --resource-group my-resource-group \ ---cluster-name mycluster --name myextension --extension-type microsoft.openservicemesh \ ---scope cluster --release-train stable -""" diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index a1cae54b9b3..0f205302699 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -131,11 +131,8 @@ CONST_APP_ROUTING_NONE_NGINX, CONST_GPU_DRIVER_TYPE_CUDA, CONST_GPU_DRIVER_TYPE_GRID, - CONST_K8S_EXTENSION_ACTION_MOD_NAME, -) -from azext_aks_preview._helpers import ( - get_k8s_extension_module, ) + from azext_aks_preview._validators import ( validate_acr, validate_addon, @@ -2332,63 +2329,6 @@ def load_arguments(self, _): help='Space-separated additional endpoint(s) to perform the connectivity check.', validator=validate_custom_endpoints) - with self.argument_context('aks extension') as c: - c.argument('location', - validator=get_default_location_from_resource_group) - c.argument('name', - options_list=['--name', '-n'], - help='Name of the extension instance') - c.argument('extension_type', - options_list=['--extension-type', '-t'], - help='Name of the extension type.') - c.argument('cluster_name', - options_list=['--cluster-name', '-c'], - help='Name of the Kubernetes cluster') - c.argument('scope', - arg_type=get_enum_type(['cluster', 'namespace']), - help='Specify the extension scope.') - c.argument('auto_upgrade_minor_version', - arg_group="Version", - options_list=['--auto-upgrade-minor-version', '--auto-upgrade'], - arg_type=get_three_state_flag(), - help='Automatically upgrade minor version of the extension instance.') - c.argument('version', - arg_group="Version", - help='Specify the version to install for the extension instance if' - ' --auto-upgrade-minor-version is not enabled.') - c.argument('release_train', - arg_group="Version", - help='Specify the release train for the extension type.') - c.argument('configuration_settings', - arg_group="Configuration", - options_list=['--configuration-settings', '--config'], - action=k8s_extension_action_mod.AddConfigurationSettings, - nargs='+', - help='Configuration Settings as key=value pair.' - + 'Repeat parameter for each setting.' - + 'Do not use this for secrets, as this value is returned in response.') - c.argument('configuration_protected_settings', - arg_group="Configuration", - options_list=['--config-protected-settings', '--config-protected'], - action=k8s_extension_action_mod.AddConfigurationProtectedSettings, - nargs='+', - help='Configuration Protected Settings as key=value pair.' - + 'Repeat parameter for each setting. Only the key is returned in response, the value is not.') - c.argument('configuration_settings_file', - arg_group="Configuration", - options_list=['--config-settings-file', '--config-file'], - help='JSON file path for configuration-settings') - c.argument('configuration_protected_settings_file', - arg_group="Configuration", - options_list=['--config-protected-settings-file', '--config-protected-file'], - help='JSON file path for configuration-protected-settings') - c.argument('release_namespace', - help='Specify the namespace to install the extension release.') - c.argument('target_namespace', - help='Specify the target namespace to install to for the extension instance. This' - ' parameter is required if extension scope is set to \'namespace\'') - - def _get_default_install_location(exe_name): system = platform.system() if system == "Windows": From 0d33c127d583502a600816fc940de69389323923 Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 10:01:54 -0700 Subject: [PATCH 15/22] revert params changes --- src/aks-preview/azext_aks_preview/_params.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index 0f205302699..b332b792856 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -33,7 +33,6 @@ tags_type, zones_type, ) -from azure.cli.core.commands.validators import get_default_location_from_resource_group from azext_aks_preview._client_factory import CUSTOM_MGMT_AKS_PREVIEW from azext_aks_preview._completers import ( get_k8s_upgrades_completion_list, @@ -132,7 +131,6 @@ CONST_GPU_DRIVER_TYPE_CUDA, CONST_GPU_DRIVER_TYPE_GRID, ) - from azext_aks_preview._validators import ( validate_acr, validate_addon, @@ -434,8 +432,6 @@ def load_arguments(self, _): operation_group="managed_clusters", ) - k8s_extension_action_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_ACTION_MOD_NAME) - # AKS command argument configuration with self.argument_context("aks") as c: c.argument( From 22071f27fac90cd78acb49f9dbd7e5a9f904906a Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 10:02:23 -0700 Subject: [PATCH 16/22] fix style --- src/aks-preview/azext_aks_preview/_params.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index b332b792856..c6f1ed9efae 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -2325,6 +2325,7 @@ def load_arguments(self, _): help='Space-separated additional endpoint(s) to perform the connectivity check.', validator=validate_custom_endpoints) + def _get_default_install_location(exe_name): system = platform.system() if system == "Windows": From ba73275d22e78d5117cff6b3be4ce3baea90b655 Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 10:12:06 -0700 Subject: [PATCH 17/22] add custom function --- src/aks-preview/azext_aks_preview/custom.py | 59 ++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/aks-preview/azext_aks_preview/custom.py b/src/aks-preview/azext_aks_preview/custom.py index eafe732c511..600be5eed16 100644 --- a/src/aks-preview/azext_aks_preview/custom.py +++ b/src/aks-preview/azext_aks_preview/custom.py @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -# pylint: disable=too-many-lines +# pylint: disable=too-many-lines, disable=broad-except import datetime import json import os @@ -57,14 +57,18 @@ CONST_AVAILABILITY_SET, CONST_MIN_NODE_IMAGE_VERSION, CONST_ARTIFACT_SOURCE_DIRECT, + CONST_K8S_EXTENSION_CUSTOM_MOD_NAME, + CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME, ) from azext_aks_preview._helpers import ( check_is_private_link_cluster, get_cluster_snapshot_by_snapshot_id, + get_k8s_extension_module, get_nodepool_snapshot_by_snapshot_id, print_or_merge_credentials, process_message_for_run_command, check_is_monitoring_addon_enabled, + check_if_extension_type_is_in_allow_list, ) from azext_aks_preview._podidentity import ( _ensure_managed_identity_operator_permission, @@ -3704,3 +3708,56 @@ def aks_check_network_outbound( instance_id, vm_name, custom_endpoints) + + +def create_k8s_extension( + cmd, + client, + resource_group_name, + cluster_name, + name, + extension_type, + scope=None, + auto_upgrade_minor_version=None, + release_train=None, + version=None, + target_namespace=None, + release_namespace=None, + configuration_settings=None, + configuration_protected_settings=None, + configuration_settings_file=None, + configuration_protected_settings_file=None, + no_wait=False, +): + if not check_if_extension_type_is_in_allow_list(extension_type.lower()): + raise ValidationError(f"Failed to install {extension_type.lower()}" + + "as it is not in allowed list of extension types") + + k8s_extension_custom_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_CUSTOM_MOD_NAME) + client_factory = get_k8s_extension_module(CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME) + client = client_factory.cf_k8s_extension_operation(cmd.cli_ctx) + + try: + result = k8s_extension_custom_mod.create_k8s_extension( + cmd, + client, + resource_group_name, + cluster_name, + name=name, + cluster_type="managedClusters", + extension_type=extension_type, + auto_upgrade_minor_version=auto_upgrade_minor_version, + release_train=release_train, + scope=scope, + version=version, + target_namespace=target_namespace, + release_namespace=release_namespace, + configuration_settings=configuration_settings, + configuration_protected_settings=configuration_protected_settings, + configuration_settings_file=configuration_settings_file, + configuration_protected_settings_file=configuration_protected_settings_file, + no_wait=no_wait, + ) + return result + except Exception as ex: + logger.error("K8s extension failed to install.\nError: %s", ex) From 723a98ec772b1c0abce13ce386c1ac9665f50260 Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 10:42:32 -0700 Subject: [PATCH 18/22] add create command --- src/aks-preview/azext_aks_preview/_help.py | 35 +++++++++++ src/aks-preview/azext_aks_preview/_params.py | 61 +++++++++++++++++++ src/aks-preview/azext_aks_preview/action.py | 38 ++++++++++++ src/aks-preview/azext_aks_preview/commands.py | 4 ++ 4 files changed, 138 insertions(+) create mode 100644 src/aks-preview/azext_aks_preview/action.py diff --git a/src/aks-preview/azext_aks_preview/_help.py b/src/aks-preview/azext_aks_preview/_help.py index e08fe641ba5..2078e162b90 100644 --- a/src/aks-preview/azext_aks_preview/_help.py +++ b/src/aks-preview/azext_aks_preview/_help.py @@ -3300,3 +3300,38 @@ type: string short-summary: Additional endpoint(s) to perform the connectivity check, separated by comma. """ + +helps['aks extension'] = """ + type: group + short-summary: Commands to manage extensions in Kubernetes cluster +""" + +helps['aks extension create'] = """ + type: command + short-summary: Creates the Kubernetes extension instance on the managed cluster. Please refer to the example at the end to see how to create a k8s extension + long-summary: Create a Kubernetes Extension. \ +The output includes secrets that you must protect. Be sure that you do not include these secrets in your \ + source control. Also verify that no secrets are present in the logs of your command or script. \ + For additional information, see http://aka.ms/clisecrets. + parameters: + - name: --extension-type -t + type: string + short-summary: Name of the extension type + - name: --cluster-name -c + type: string + short-summary: Name of the AKS cluster + - name: --name -n + type: string + short-summary: Name of the extension instance + - name: --scope + type: string + short-summary: specify scope of the extension type, takes in name or cluster as the scope + - name: --release-train + type: string + short-summary: specify the release train for the extension type + examples: + - name: Install K8s extension on AKS cluster + text: az aks extension create --resource-group my-resource-group \ +--cluster-name mycluster --name myextension --extension-type microsoft.openservicemesh \ +--scope cluster --release-train stable +""" diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index c6f1ed9efae..2b98326a406 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -33,6 +33,7 @@ tags_type, zones_type, ) +from azure.cli.core.commands.validators import get_default_location_from_resource_group from azext_aks_preview._client_factory import CUSTOM_MGMT_AKS_PREVIEW from azext_aks_preview._completers import ( get_k8s_upgrades_completion_list, @@ -218,6 +219,10 @@ CONST_STORAGE_POOL_OPTION_NVME, CONST_STORAGE_POOL_OPTION_SSD, ) +from .action import ( + AddConfigurationSettings, + AddConfigurationProtectedSettings, +) from knack.arguments import CLIArgumentType # candidates for enumeration @@ -2325,6 +2330,62 @@ def load_arguments(self, _): help='Space-separated additional endpoint(s) to perform the connectivity check.', validator=validate_custom_endpoints) + with self.argument_context('aks extension') as c: + c.argument('location', + validator=get_default_location_from_resource_group) + c.argument('name', + options_list=['--name', '-n'], + help='Name of the extension instance') + c.argument('extension_type', + options_list=['--extension-type', '-t'], + help='Name of the extension type.') + c.argument('cluster_name', + options_list=['--cluster-name', '-c'], + help='Name of the Kubernetes cluster') + c.argument('scope', + arg_type=get_enum_type(['cluster', 'namespace']), + help='Specify the extension scope.') + c.argument('auto_upgrade_minor_version', + arg_group="Version", + options_list=['--auto-upgrade-minor-version', '--auto-upgrade'], + arg_type=get_three_state_flag(), + help='Automatically upgrade minor version of the extension instance.') + c.argument('version', + arg_group="Version", + help='Specify the version to install for the extension instance if' + ' --auto-upgrade-minor-version is not enabled.') + c.argument('release_train', + arg_group="Version", + help='Specify the release train for the extension type.') + c.argument('configuration_settings', + arg_group="Configuration", + options_list=['--configuration-settings', '--config'], + action=AddConfigurationSettings, + nargs='+', + help='Configuration Settings as key=value pair.' + + 'Repeat parameter for each setting.' + + 'Do not use this for secrets, as this value is returned in response.') + c.argument('configuration_protected_settings', + arg_group="Configuration", + options_list=['--config-protected-settings', '--config-protected'], + action=AddConfigurationProtectedSettings, + nargs='+', + help='Configuration Protected Settings as key=value pair.' + + 'Repeat parameter for each setting. Only the key is returned in response, the value is not.') + c.argument('configuration_settings_file', + arg_group="Configuration", + options_list=['--config-settings-file', '--config-file'], + help='JSON file path for configuration-settings') + c.argument('configuration_protected_settings_file', + arg_group="Configuration", + options_list=['--config-protected-settings-file', '--config-protected-file'], + help='JSON file path for configuration-protected-settings') + c.argument('release_namespace', + help='Specify the namespace to install the extension release.') + c.argument('target_namespace', + help='Specify the target namespace to install to for the extension instance. This' + ' parameter is required if extension scope is set to \'namespace\'') + def _get_default_install_location(exe_name): system = platform.system() diff --git a/src/aks-preview/azext_aks_preview/action.py b/src/aks-preview/azext_aks_preview/action.py new file mode 100644 index 00000000000..80850f6e9f8 --- /dev/null +++ b/src/aks-preview/azext_aks_preview/action.py @@ -0,0 +1,38 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import argparse +from azure.cli.core.azclierror import ArgumentUsageError + +# pylint: disable=protected-access, too-few-public-methods + + +class AddConfigurationSettings(argparse._AppendAction): + + def __call__(self, parser, namespace, values, option_string=None): + settings = {} + for item in values: + try: + key, value = item.split('=', 1) + settings[key] = value + except ValueError as ex: + raise ArgumentUsageError('Usage error: {} configuration_setting_key=configuration_setting_value'. + format(option_string)) from ex + super().__call__(parser, namespace, settings, option_string) + + +# pylint: disable=protected-access, too-few-public-methods +class AddConfigurationProtectedSettings(argparse._AppendAction): + + def __call__(self, parser, namespace, values, option_string=None): + prot_settings = {} + for item in values: + try: + key, value = item.split('=', 1) + prot_settings[key] = value + except ValueError as ex: + raise ArgumentUsageError('Usage error: {} configuration_protected_setting_key=' + 'configuration_protected_setting_value'.format(option_string)) from ex + super().__call__(parser, namespace, prot_settings, option_string) diff --git a/src/aks-preview/azext_aks_preview/commands.py b/src/aks-preview/azext_aks_preview/commands.py index 183738999f6..f716ff8e581 100644 --- a/src/aks-preview/azext_aks_preview/commands.py +++ b/src/aks-preview/azext_aks_preview/commands.py @@ -453,3 +453,7 @@ def load_command_table(self, _): "aks check-network", managed_clusters_sdk, client_factory=cf_managed_clusters ) as g: g.custom_command("outbound", "aks_check_network_outbound") + + with self.command_group("aks extension", managed_clusters_sdk, client_factory=cf_managed_clusters) \ + as g: + g.custom_command('create', 'create_k8s_extension', supports_no_wait=True) From ed4aec9d39796c74bb407d4ecf64f4a8e06508bf Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 11:26:58 -0700 Subject: [PATCH 19/22] add aks update --- src/aks-preview/azext_aks_preview/_help.py | 33 ++++++++++++++ src/aks-preview/azext_aks_preview/_params.py | 5 +++ src/aks-preview/azext_aks_preview/commands.py | 1 + src/aks-preview/azext_aks_preview/custom.py | 43 +++++++++++++++++++ 4 files changed, 82 insertions(+) diff --git a/src/aks-preview/azext_aks_preview/_help.py b/src/aks-preview/azext_aks_preview/_help.py index 2078e162b90..2577a0990a5 100644 --- a/src/aks-preview/azext_aks_preview/_help.py +++ b/src/aks-preview/azext_aks_preview/_help.py @@ -3335,3 +3335,36 @@ --cluster-name mycluster --name myextension --extension-type microsoft.openservicemesh \ --scope cluster --release-train stable """ + +helps['aks extension update'] = """ + type: command + short-summary: Update mutable properties of a Kubernetes Extension. + long-summary: For update to ConfigSettings and ConfigProtectedSettings, please \ +refer to documentation of the cluster extension service to check update to these \ +properties is supported before updating these properties. \ +The output includes secrets that you must protect. Be sure that you do not include these secrets in your \ + source control. Also verify that no secrets are present in the logs of your command or script. \ + For additional information, see http://aka.ms/clisecrets. + parameters: + - name: --cluster-name -c + type: string + short-summary: Name of the AKS cluster + - name: --name -n + type: string + short-summary: Name of the extension instance + - name: --release-train + type: string + short-summary: specify the release train for the extension type + - name: --version + type: string + short-summary: Specify the version to install for the extension instance if --auto-upgrade-minor-version is not enabled. + examples: + - name: Update K8s extension on AKS cluster + text: az aks extension update --resource-group my-resource-group \ +--cluster-name mycluster --name myextension --auto-upgrade true/false \ +--version extension-version --release-train stable \ +--configuration-settings settings-key=settings-value \ +--config-protected-settings protected-settings-key=protected-value \ +--config-settings-file=config-settings-file \ +--config-protected-file=protected-settings-file +""" diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index 2b98326a406..12487ee641f 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -2386,6 +2386,11 @@ def load_arguments(self, _): help='Specify the target namespace to install to for the extension instance. This' ' parameter is required if extension scope is set to \'namespace\'') + with self.argument_context("aks extension update") as c: + c.argument('yes', + options_list=['--yes', '-y'], + help='Ignore confirmation prompts') + def _get_default_install_location(exe_name): system = platform.system() diff --git a/src/aks-preview/azext_aks_preview/commands.py b/src/aks-preview/azext_aks_preview/commands.py index f716ff8e581..965fe8a4477 100644 --- a/src/aks-preview/azext_aks_preview/commands.py +++ b/src/aks-preview/azext_aks_preview/commands.py @@ -457,3 +457,4 @@ def load_command_table(self, _): with self.command_group("aks extension", managed_clusters_sdk, client_factory=cf_managed_clusters) \ as g: g.custom_command('create', 'create_k8s_extension', supports_no_wait=True) + g.custom_command('update', 'update_k8s_extension', supports_no_wait=True) diff --git a/src/aks-preview/azext_aks_preview/custom.py b/src/aks-preview/azext_aks_preview/custom.py index 600be5eed16..688a950c653 100644 --- a/src/aks-preview/azext_aks_preview/custom.py +++ b/src/aks-preview/azext_aks_preview/custom.py @@ -3761,3 +3761,46 @@ def create_k8s_extension( return result except Exception as ex: logger.error("K8s extension failed to install.\nError: %s", ex) + + +def update_k8s_extension( + cmd, + client, + resource_group_name, + cluster_name, + name, + auto_upgrade_minor_version=None, + release_train=None, + version=None, + configuration_settings=None, + configuration_protected_settings=None, + configuration_settings_file=None, + configuration_protected_settings_file=None, + no_wait=False, + yes=False, +): + k8s_extension_custom_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_CUSTOM_MOD_NAME) + client_factory = get_k8s_extension_module(CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME) + client = client_factory.cf_k8s_extension_operation(cmd.cli_ctx) + + try: + result = k8s_extension_custom_mod.update_k8s_extension( + cmd, + client, + resource_group_name, + cluster_name, + name, + "managedClusters", + auto_upgrade_minor_version=auto_upgrade_minor_version, + release_train=release_train, + version=version, + configuration_settings=configuration_settings, + configuration_protected_settings=configuration_protected_settings, + configuration_settings_file=configuration_settings_file, + configuration_protected_settings_file=configuration_protected_settings_file, + no_wait=no_wait, + yes=yes, + ) + return result + except Exception as ex: + logger.error("K8s extension failed to patch.\nError: %s", ex) From 07c5a5df06f6e59f71fe17ca66368b3be9838d58 Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 11:37:53 -0700 Subject: [PATCH 20/22] add delete command --- src/aks-preview/azext_aks_preview/_help.py | 15 +++++++++ src/aks-preview/azext_aks_preview/_params.py | 7 +++++ src/aks-preview/azext_aks_preview/commands.py | 1 + src/aks-preview/azext_aks_preview/custom.py | 31 +++++++++++++++++++ 4 files changed, 54 insertions(+) diff --git a/src/aks-preview/azext_aks_preview/_help.py b/src/aks-preview/azext_aks_preview/_help.py index 2577a0990a5..4b6c70eb550 100644 --- a/src/aks-preview/azext_aks_preview/_help.py +++ b/src/aks-preview/azext_aks_preview/_help.py @@ -3368,3 +3368,18 @@ --config-settings-file=config-settings-file \ --config-protected-file=protected-settings-file """ + +helps['aks extension delete'] = """ + type: command + short-summary: Delete a Kubernetes Extension. + parameters: + - name: --cluster-name -c + type: string + short-summary: Name of the AKS cluster + - name: --name -n + type: string + short-summary: Name of the extension instance + examples: + - name: Delete an existing Kubernetes extension on AKS cluster + text: az aks extension delete --resource-group resource-group --cluster-name cluster --name ext +""" \ No newline at end of file diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index 12487ee641f..e21bec54977 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -2391,6 +2391,13 @@ def load_arguments(self, _): options_list=['--yes', '-y'], help='Ignore confirmation prompts') + with self.argument_context("aks extension delete") as c: + c.argument('yes', + options_list=['--yes', '-y'], + help='Ignore confirmation prompts') + c.argument('force', + help='Specify whether to force delete the extension from the cluster.') + def _get_default_install_location(exe_name): system = platform.system() diff --git a/src/aks-preview/azext_aks_preview/commands.py b/src/aks-preview/azext_aks_preview/commands.py index 965fe8a4477..5d651d4cb73 100644 --- a/src/aks-preview/azext_aks_preview/commands.py +++ b/src/aks-preview/azext_aks_preview/commands.py @@ -458,3 +458,4 @@ def load_command_table(self, _): as g: g.custom_command('create', 'create_k8s_extension', supports_no_wait=True) g.custom_command('update', 'update_k8s_extension', supports_no_wait=True) + g.custom_command('delete', 'delete_k8s_extension', supports_no_wait=True) diff --git a/src/aks-preview/azext_aks_preview/custom.py b/src/aks-preview/azext_aks_preview/custom.py index 688a950c653..edb67a3206d 100644 --- a/src/aks-preview/azext_aks_preview/custom.py +++ b/src/aks-preview/azext_aks_preview/custom.py @@ -3804,3 +3804,34 @@ def update_k8s_extension( return result except Exception as ex: logger.error("K8s extension failed to patch.\nError: %s", ex) + + +def delete_k8s_extension( + cmd, + client, + resource_group_name, + cluster_name, + name, + no_wait=False, + yes=False, + force=False, +): + k8s_extension_custom_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_CUSTOM_MOD_NAME) + client_factory = get_k8s_extension_module(CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME) + client = client_factory.cf_k8s_extension_operation(cmd.cli_ctx) + + try: + result = k8s_extension_custom_mod.delete_k8s_extension( + cmd, + client, + resource_group_name, + cluster_name, + name, + "managedClusters", + no_wait=no_wait, + yes=yes, + force=force, + ) + return result + except Exception as ex: + logger.error("Failed to delete K8s extension.\nError: %s", ex) From 549c66cda1d239bcb19d2b800b80b32f1a8a2441 Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Wed, 23 Apr 2025 11:51:42 -0700 Subject: [PATCH 21/22] add show command --- src/aks-preview/azext_aks_preview/_help.py | 22 ++++++++++++++++++- src/aks-preview/azext_aks_preview/commands.py | 16 ++++++++++++++ src/aks-preview/azext_aks_preview/custom.py | 18 +++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/aks-preview/azext_aks_preview/_help.py b/src/aks-preview/azext_aks_preview/_help.py index 4b6c70eb550..5933fe972ef 100644 --- a/src/aks-preview/azext_aks_preview/_help.py +++ b/src/aks-preview/azext_aks_preview/_help.py @@ -3382,4 +3382,24 @@ examples: - name: Delete an existing Kubernetes extension on AKS cluster text: az aks extension delete --resource-group resource-group --cluster-name cluster --name ext -""" \ No newline at end of file +""" + +helps['aks extension show'] = """ + type: command + short-summary: Show a Kubernetes Extension + long-summary: Show a Kubernetes Extension including its properties. \ +The output includes secrets that you must protect. Be sure that you do not include these secrets in your \ + source control. Also verify that no secrets are present in the logs of your command or script. \ + For additional information, see http://aka.ms/clisecrets. + parameters: + - name: --cluster-name -c + type: string + short-summary: Name of the AKS cluster + - name: --name -n + type: string + short-summary: Name of the extension instance + examples: + - name: Show details of a Kubernetes Extension + text: az aks extension show --resource-group my-resource-group \ +--cluster-name mycluster --name myextension +""" diff --git a/src/aks-preview/azext_aks_preview/commands.py b/src/aks-preview/azext_aks_preview/commands.py index 5d651d4cb73..04afb8ca042 100644 --- a/src/aks-preview/azext_aks_preview/commands.py +++ b/src/aks-preview/azext_aks_preview/commands.py @@ -16,6 +16,11 @@ cf_machines, cf_operations, ) + +from azext_aks_preview._consts import ( + CONST_K8S_EXTENSION_FORMAT_MOD_NAME, +) + from azext_aks_preview._format import ( aks_addon_list_available_table_format, aks_addon_list_table_format, @@ -38,6 +43,11 @@ aks_mesh_revisions_table_format, aks_mesh_upgrades_table_format, ) + +from azext_aks_preview._helpers import ( + get_k8s_extension_module, +) + from knack.log import get_logger logger = get_logger(__name__) @@ -82,6 +92,7 @@ def _patch_custom_cas_in_security_profile(security_profile): # pylint: disable=too-many-statements def load_command_table(self, _): + k8s_extension_format_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_FORMAT_MOD_NAME) managed_clusters_sdk = CliCommandType( operations_tmpl="azext_aks_preview.vendored_sdks.azure_mgmt_preview_aks." "operations._managed_clusters_operations#ManagedClustersOperations.{}", @@ -459,3 +470,8 @@ def load_command_table(self, _): g.custom_command('create', 'create_k8s_extension', supports_no_wait=True) g.custom_command('update', 'update_k8s_extension', supports_no_wait=True) g.custom_command('delete', 'delete_k8s_extension', supports_no_wait=True) + g.custom_show_command( + 'show', + 'show_k8s_extension', + table_transformer=k8s_extension_format_mod.k8s_extension_show_table_format + ) diff --git a/src/aks-preview/azext_aks_preview/custom.py b/src/aks-preview/azext_aks_preview/custom.py index edb67a3206d..c11345472c8 100644 --- a/src/aks-preview/azext_aks_preview/custom.py +++ b/src/aks-preview/azext_aks_preview/custom.py @@ -3835,3 +3835,21 @@ def delete_k8s_extension( return result except Exception as ex: logger.error("Failed to delete K8s extension.\nError: %s", ex) + + +def show_k8s_extension(cmd, client, resource_group_name, cluster_name, name): + k8s_extension_custom_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_CUSTOM_MOD_NAME) + client_factory = get_k8s_extension_module(CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME) + client = client_factory.cf_k8s_extension_operation(cmd.cli_ctx) + + try: + result = k8s_extension_custom_mod.show_k8s_extension( + client, + resource_group_name, + cluster_name, + name, + "managedClusters", + ) + return result + except Exception as ex: + logger.error("Failed to get K8s extension.\nError: %s", ex) From f2613bf34a85638623e12ae1cd16bd76f251e4b1 Mon Sep 17 00:00:00 2001 From: sophie zhao Date: Sun, 27 Apr 2025 15:12:34 -0700 Subject: [PATCH 22/22] sync with latest commands.py and helper.py --- src/aks-preview/azext_aks_preview/_format.py | 73 ++++++++ src/aks-preview/azext_aks_preview/_params.py | 177 ++++++++++++++++++ src/aks-preview/azext_aks_preview/action.py | 3 + src/aks-preview/azext_aks_preview/commands.py | 87 +++++++-- 4 files changed, 327 insertions(+), 13 deletions(-) diff --git a/src/aks-preview/azext_aks_preview/_format.py b/src/aks-preview/azext_aks_preview/_format.py index b0b0ced6caa..c7323eb6118 100644 --- a/src/aks-preview/azext_aks_preview/_format.py +++ b/src/aks-preview/azext_aks_preview/_format.py @@ -373,3 +373,76 @@ def _format_mesh_revision_entry(revision): } flattened.append(item) return flattened + + +def aks_extension_list_table_format(results): + """Format a list of K8s extensions as summary results for display with "-o table". """ + return [_get_table_row(result) for result in results] + + +def aks_extension_show_table_format(result): + """Format a K8s extension as summary results for display with "-o table". """ + return _get_table_row(result) + + +def _get_table_row(result): + return OrderedDict([ + ('name', result['name']), + ('extensionType', result.get('extensionType', '')), + ('version', result.get('version', '')), + ('provisioningState', result.get('provisioningState', '')), + ('lastModifiedAt', result.get('systemData', {}).get('lastModifiedAt', '')), + ('isSystemExtension', result.get('isSystemExtension', '')), + ]) + + +def aks_extension_types_list_table_format(results): + """Format a list of K8s extension types as summary results for display with "-o table". """ + return [_get_extension_type_table_row(result) for result in results] + + +def aks_extension_type_show_table_format(result): + """Format a K8s extension type as summary results for display with "-o table". """ + return _get_extension_type_table_row(result) + + +def _get_extension_type_table_row(result): + # Populate the values to be returned if they are not undefined + clusterTypes = '' + if result['properties']['supportedClusterTypes'] is not None: + clusterTypes = ', '.join(result['properties']['supportedClusterTypes']) + + name = result['name'] + defaultScope, allowMultInstances, defaultReleaseNamespace = '', '', '' + if result['properties']['supportedScopes']: + defaultScope = result['properties']['supportedScopes']['defaultScope'] + if result['properties']['supportedScopes']['clusterScopeSettings'] is not None: + clusterScopeSettings = result['properties']['supportedScopes']['clusterScopeSettings'] + allowMultInstances = clusterScopeSettings['allowMultipleInstances'] + defaultReleaseNamespace = clusterScopeSettings['defaultReleaseNamespace'] + + retVal = OrderedDict([ + ('name', name), + ('defaultScope', defaultScope), + ('clusterTypes', clusterTypes), + ('allowMultipleInstances', allowMultInstances), + ('defaultReleaseNamespace', defaultReleaseNamespace) + ]) + + return retVal + + +def aks_extension_type_versions_list_table_format(results): + """Format a list of K8s extension type versions as summary results for display with "-o table". """ + return [_get_extension_type_versions_table_row(result) for result in results] + + +def aks_extension_type_version_show_table_format(results): + """Format a K8s extension type version as summary results for display with "-o table". """ + return _get_extension_type_versions_table_row(results) + + +def _get_extension_type_versions_table_row(result): + return OrderedDict([ + ('versions', result['properties']['version']) + ]) diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index e21bec54977..363286b981d 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -132,6 +132,7 @@ CONST_GPU_DRIVER_TYPE_CUDA, CONST_GPU_DRIVER_TYPE_GRID, ) + from azext_aks_preview._validators import ( validate_acr, validate_addon, @@ -219,10 +220,12 @@ CONST_STORAGE_POOL_OPTION_NVME, CONST_STORAGE_POOL_OPTION_SSD, ) + from .action import ( AddConfigurationSettings, AddConfigurationProtectedSettings, ) + from knack.arguments import CLIArgumentType # candidates for enumeration @@ -2330,7 +2333,11 @@ def load_arguments(self, _): help='Space-separated additional endpoint(s) to perform the connectivity check.', validator=validate_custom_endpoints) + # Reference: https://learn.microsoft.com/en-us/cli/azure/k8s-extension?view=azure-cli-latest with self.argument_context('aks extension') as c: + c.argument('resource_group_name', + options_list=['--resource-group', '-g'], + help='Name of resource group.') c.argument('location', validator=get_default_location_from_resource_group) c.argument('name', @@ -2398,6 +2405,176 @@ def load_arguments(self, _): c.argument('force', help='Specify whether to force delete the extension from the cluster.') + # Reference: https://learn.microsoft.com/en-us/cli/azure/k8s-extension/extension-types?view=azure-cli-latest + with self.argument_context("aks extension-type") as c: + c.argument('resource_group_name', + options_list=['--resource-group', '-g'], + help='Name of resource group.') + c.argument('cluster_name', + options_list=['--cluster-name', '-c'], + help='Name of the Kubernetes cluster') + c.argument('extension_type', + options_list=['--extension-type', '-t'], + help='Name of the extension type.') + c.argument('location', + validator=get_default_location_from_resource_group, + help='Name of the location. Values from: `az account list-locations`') + c.argument('version', + help='Version for the extension type.') + c.argument('major_version', + help='Filter results by only the major version of an extension type.' + + 'For example if 1 is specified, all versions with major version 1 (1.1, 1.1.2) will be shown.' + + 'The default value is None') + c.argument('release_train', + arg_group="Version", + help='Specify the release train for the extension type.') + c.argument('show_latest', + arg_type=get_three_state_flag(), + help='Filter results by only the latest version.' + + 'For example, if this flag is used the latest version of the extensionType will be shown.') + # AKS loadbalancer command parameter configuration + with self.argument_context("aks loadbalancer add") as c: + c.argument( + "name", + options_list=["--name", "-n"], + help="Name of the load balancer configuration. Required.", + ) + c.argument( + "primary_agent_pool_name", + options_list=["--primary-agent-pool-name", "-p"], + help=( + "Name of the primary agent pool for this load balancer. " + "All nodes in this pool will be added to the load balancer. Required." + ), + ) + c.argument( + "allow_service_placement", + options_list=["--allow-service-placement", "-a"], + arg_type=get_three_state_flag(), + help="Whether to automatically place services on the load balancer. Default is true.", + ) + c.argument( + "aks_custom_headers", + help="Send custom headers. When specified, format should be Key1=Value1,Key2=Value2.", + ) + c.argument( + "service_label_selector", + options_list=["--service-label-selector", "-l"], + help=( + "Only services that match this selector can be placed on this load balancer. " + "Format: key1=value1,key2=value2 for simple selectors, " + "or key1 In val1 val2,key2 Exists for advanced expressions." + ), + ) + c.argument( + "service_namespace_selector", + options_list=["--service-namespace-selector", "-s"], + help=( + "Services created in namespaces that match the selector can be placed on this load balancer. " + "Format: key1=value1,key2=value2 for simple selectors, " + "or key1 In val1 val2,key2 Exists for advanced expressions." + ), + ) + c.argument( + "node_selector", + options_list=["--node-selector", "-d"], + help=( + "Nodes that match this selector will be possible members of this load balancer. " + "Format: key1=value1,key2=value2 for simple selectors, " + "or key1 In val1 val2,key2 Exists for advanced expressions." + ), + ) + + with self.argument_context("aks loadbalancer rebalance-nodes") as c: + c.argument( + "resource_group_name", + options_list=["--resource-group", "-g"], + help="Name of resource group.", + id_part="resource_group", + configured_default="aks", + ) + c.argument( + "cluster_name", + options_list=["--name", "-n"], + help="Name of the managed cluster.", + ) + c.argument( + "load_balancer_names", + options_list=["--load-balancer-names", "--lb-names"], + nargs="+", + help=( + "Space-separated list of load balancer names to rebalance. " + "If not specified, all load balancers will be rebalanced." + ), + ) + c.argument( + "no_wait", help="Do not wait for the long-running operation to finish." + ) + + with self.argument_context("aks loadbalancer update") as c: + c.argument( + "name", + options_list=["--name", "-n"], + help="Name of the public load balancer. Required.", + ) + c.argument( + "primary_agent_pool_name", + options_list=["--primary-agent-pool-name", "-p"], + help=( + "Name of the primary agent pool for this load balancer. " + "All nodes in this pool will be added to the load balancer." + ), + ) + c.argument( + "allow_service_placement", + options_list=["--allow-service-placement", "-a"], + arg_type=get_three_state_flag(), + help="Whether to automatically place services on the load balancer. Default is true.", + ) + c.argument( + "aks_custom_headers", + help="Send custom headers. When specified, format should be Key1=Value1,Key2=Value2.", + ) + c.argument( + "service_label_selector", + options_list=["--service-label-selector", "-l"], + help=( + "Only services that match this selector can be placed on this load balancer. " + "Format: key1=value1,key2=value2 for simple selectors, " + "or key1 In val1 val2,key2 Exists for advanced expressions." + ), + ) + c.argument( + "service_namespace_selector", + options_list=["--service-namespace-selector", "-s"], + help=( + "Services created in namespaces that match the selector can be placed on this load balancer. " + "Format: key1=value1,key2=value2 for simple selectors, " + "or key1 In val1 val2,key2 Exists for advanced expressions." + ), + ) + c.argument( + "node_selector", + options_list=["--node-selector", "-d"], + help=( + "Nodes that match this selector will be possible members of this load balancer. " + "Format: key1=value1,key2=value2 for simple selectors, " + "or key1 In val1 val2,key2 Exists for advanced expressions." + ), + ) + + # Define parameters for show and delete commands + for scope in [ + "aks loadbalancer show", + "aks loadbalancer delete", + ]: + with self.argument_context(scope) as c: + c.argument( + "name", + options_list=["--name", "-n"], + help="Name of the load balancer configuration. Required.", + ) + def _get_default_install_location(exe_name): system = platform.system() diff --git a/src/aks-preview/azext_aks_preview/action.py b/src/aks-preview/azext_aks_preview/action.py index 80850f6e9f8..73fd3dfefde 100644 --- a/src/aks-preview/azext_aks_preview/action.py +++ b/src/aks-preview/azext_aks_preview/action.py @@ -8,6 +8,9 @@ # pylint: disable=protected-access, too-few-public-methods +# Reference: https://github.com/Azure/azure-cli-extensions/blob/main/src/k8s-extension/azext_k8s_extension/action.py +# TODO: Update this file if there are changes to this class in the k8s-extensions folder + class AddConfigurationSettings(argparse._AppendAction): diff --git a/src/aks-preview/azext_aks_preview/commands.py b/src/aks-preview/azext_aks_preview/commands.py index 04afb8ca042..26e5a144f4e 100644 --- a/src/aks-preview/azext_aks_preview/commands.py +++ b/src/aks-preview/azext_aks_preview/commands.py @@ -15,10 +15,7 @@ cf_trustedaccess_role_binding, cf_machines, cf_operations, -) - -from azext_aks_preview._consts import ( - CONST_K8S_EXTENSION_FORMAT_MOD_NAME, + cf_load_balancers, ) from azext_aks_preview._format import ( @@ -42,10 +39,12 @@ aks_versions_table_format, aks_mesh_revisions_table_format, aks_mesh_upgrades_table_format, -) - -from azext_aks_preview._helpers import ( - get_k8s_extension_module, + aks_extension_list_table_format, + aks_extension_show_table_format, + aks_extension_types_list_table_format, + aks_extension_type_show_table_format, + aks_extension_type_versions_list_table_format, + aks_extension_type_version_show_table_format, ) from knack.log import get_logger @@ -92,7 +91,6 @@ def _patch_custom_cas_in_security_profile(security_profile): # pylint: disable=too-many-statements def load_command_table(self, _): - k8s_extension_format_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_FORMAT_MOD_NAME) managed_clusters_sdk = CliCommandType( operations_tmpl="azext_aks_preview.vendored_sdks.azure_mgmt_preview_aks." "operations._managed_clusters_operations#ManagedClustersOperations.{}", @@ -207,6 +205,18 @@ def load_command_table(self, _): g.custom_command("update", "aks_maintenanceconfiguration_update") g.custom_command("delete", "aks_maintenanceconfiguration_delete") + # AKS loadbalancer commands + with self.command_group( + "aks loadbalancer", + client_factory=cf_load_balancers, + ) as g: + g.custom_command("list", "aks_loadbalancer_list") + g.custom_show_command("show", "aks_loadbalancer_show") + g.custom_command("add", "aks_loadbalancer_add") + g.custom_command("update", "aks_loadbalancer_update") + g.custom_command("delete", "aks_loadbalancer_delete") + g.custom_command("rebalance-nodes", "aks_loadbalancer_rebalance_nodes") + # AKS addon commands with self.command_group( "aks addon", managed_clusters_sdk, client_factory=cf_managed_clusters @@ -465,13 +475,64 @@ def load_command_table(self, _): ) as g: g.custom_command("outbound", "aks_check_network_outbound") - with self.command_group("aks extension", managed_clusters_sdk, client_factory=cf_managed_clusters) \ - as g: + with self.command_group( + "aks extension", managed_clusters_sdk, client_factory=cf_managed_clusters + ) as g: g.custom_command('create', 'create_k8s_extension', supports_no_wait=True) - g.custom_command('update', 'update_k8s_extension', supports_no_wait=True) g.custom_command('delete', 'delete_k8s_extension', supports_no_wait=True) + g.custom_command( + 'list', + 'list_k8s_extension', + table_transformer=aks_extension_list_table_format + ) g.custom_show_command( 'show', 'show_k8s_extension', - table_transformer=k8s_extension_format_mod.k8s_extension_show_table_format + table_transformer=aks_extension_show_table_format + ) + g.custom_command('update', 'update_k8s_extension', supports_no_wait=True) + + with self.command_group( + "aks extension-type", managed_clusters_sdk, client_factory=cf_managed_clusters + ) as g: + g.custom_command( + 'list-by-location', + 'list_k8s_extension_types_by_location', + table_transformer=aks_extension_types_list_table_format + ) + g.custom_command( + 'show-by-location', + 'show_k8s_extension_type_by_location', + table_transformer=aks_extension_type_show_table_format + ) + g.custom_command( + 'list-versions-by-location', + 'list_k8s_extension_type_versions_by_location', + table_transformer=aks_extension_type_versions_list_table_format + ) + g.custom_command( + 'show-version-by-location', + 'show_k8s_extension_type_version_by_location', + table_transformer=aks_extension_type_version_show_table_format + ) + + g.custom_command( + 'list-by-cluster', + 'list_k8s_extension_types_by_cluster', + table_transformer=aks_extension_types_list_table_format + ) + g.custom_command( + 'show-by-cluster', + 'show_k8s_extension_type_by_cluster', + table_transformer=aks_extension_type_show_table_format + ) + g.custom_command( + 'list-versions-by-cluster', + 'list_k8s_extension_type_versions_by_cluster', + table_transformer=aks_extension_type_versions_list_table_format + ) + g.custom_command( + 'show-version-by-cluster', + 'show_k8s_extension_type_version_by_cluster', + table_transformer=aks_extension_type_version_show_table_format )