From c360569104c9acb06a1932d3026de65914c022f0 Mon Sep 17 00:00:00 2001 From: Julien Stroheker Date: Thu, 30 Nov 2017 19:25:56 -0600 Subject: [PATCH] AKS/ACI - Add install-connector and remove-connector commands (#4996) --- src/command_modules/azure-cli-acs/HISTORY.rst | 4 + .../azure/cli/command_modules/acs/_help.py | 24 +++++ .../azure/cli/command_modules/acs/_params.py | 13 +++ .../azure/cli/command_modules/acs/commands.py | 4 + .../azure/cli/command_modules/acs/custom.py | 90 +++++++++++++++++++ src/command_modules/azure-cli-acs/setup.py | 2 +- 6 files changed, 136 insertions(+), 1 deletion(-) diff --git a/src/command_modules/azure-cli-acs/HISTORY.rst b/src/command_modules/azure-cli-acs/HISTORY.rst index 5f60f976fff..9b96b2b721b 100644 --- a/src/command_modules/azure-cli-acs/HISTORY.rst +++ b/src/command_modules/azure-cli-acs/HISTORY.rst @@ -3,6 +3,10 @@ Release History =============== +2.0.21 +++++++ +* add `az aks install-connector` and `az aks remove-connector` commands + 2.0.20 ++++++ * `acs create`: emit out an actionable error if provisioning application failed for lack of permissions diff --git a/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/_help.py b/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/_help.py index 6ad10a7e866..e857fbe79a8 100644 --- a/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/_help.py +++ b/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/_help.py @@ -79,3 +79,27 @@ type: command short-summary: Wait for a Kubernetes cluster to reach a desired state. """ +helps['aks install-connector'] = """ + type: command + short-summary: Deploy the ACI-Connector to a AKS cluster. + examples: + - name: Install the ACI-Connector to an AKS Cluster. + text: az aks install-connector --name MyManagedCluster --resource-group MyResourceGroup --connector-name MyConnector + - name: Install the ACI-Connector for Windows to an AKS Cluster. + text: az aks install-connector --name MyManagedCluster --resource-group MyResourceGroup --connector-name MyConnector --os-type Windows + - name: Install the ACI-Connector for Windows and Linux to an AKS Cluster. + text: az aks install-connector --name MyManagedCluster --resource-group MyResourceGroup --connector-name MyConnector --os-type Both + - name: Install the ACI-Connector using specific SPN. + text: az aks install-connector --name MyManagedCluster --resource-group MyResourceGroup --connector-name MyConnector --service-principal --client-secret + - name: Install the ACI-Connector from a specific custom chart. + text: az aks install-connector --name MyManagedCluster --resource-group MyResourceGroup --connector-name MyConnector --chart-url +""" +helps['aks remove-connector'] = """ + type: command + short-summary: Undeploy the ACI-Connector from an AKS cluster. + examples: + - name: Undeploy the ACI-Connector on an AKS cluster. + text: az aks remove-connector --name MyManagedCluster --resource-group MyResourceGroup --connector-name MyConnector + - name: Undeploy the ACI-Connector on an AKS cluster using the graceful mode + text: az aks remove-connector --name MyManagedCluster --resource-group MyResourceGroup --connector-name MyConnector --graceful +""" diff --git a/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/_params.py b/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/_params.py index ffc1a2b02de..b3896bb6c41 100644 --- a/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/_params.py +++ b/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/_params.py @@ -72,6 +72,10 @@ def _get_feature_in_preview_message(): orchestratorTypes = ["Custom", "DCOS", "Kubernetes", "Swarm", "DockerCE"] +aci_connector_os_type = ['Windows', 'Linux', 'Both'] + +aci_connector_chart_url = 'https://github.com/Azure/aci-connector-k8s/raw/master/charts/aci-connector.tgz' + k8s_version_arg_type = CliArgumentType(options_list=('--kubernetes-version', '-k'), metavar='KUBERNETES_VERSION') storageProfileTypes = ["StorageAccount", "ManagedDisks"] @@ -182,3 +186,12 @@ def _get_feature_in_preview_message(): default=_get_default_install_location('kubectl')) register_cli_argument('aks install-cli', 'client_version', options_list=('--client-version',), validator=validate_k8s_client_version) +register_cli_argument('aks install-connector', 'os_type', help='The OS type of the connector', **enum_choice_list(aci_connector_os_type)) +register_cli_argument('aks install-connector', 'chart_url', help='URL to the chart', default=aci_connector_chart_url) +register_cli_argument('aks install-connector', 'connector_name', help='The name for the ACI Connector') +register_cli_argument('aks install-connector', 'service_principal', help='Service principal for making calls into Azure APIs. If not set, auto generate a new service principal of Contributor role, and save it locally for reusing') +register_cli_argument('aks install-connector', 'client_secret', help='Client secret to use with the service principal for making calls to Azure APIs') +register_cli_argument('aks remove-connector', 'os_type', help='The OS type of the connector', **enum_choice_list(aci_connector_os_type)) +register_cli_argument('aks remove-connector', 'graceful', help='Mention if you want to drain/uncordon your aci-connector to move your applications', action='store_true') +register_cli_argument('aks remove-connector', 'connector_name', help='The name for the ACI Connector') +register_cli_argument('aks remove-connector', 'resource_group', arg_type=resource_group_name_type, help='The name of the resource group where the AKS cluster is deployed') diff --git a/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/commands.py b/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/commands.py index aa9f17fc323..26073183d4b 100644 --- a/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/commands.py +++ b/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/commands.py @@ -109,6 +109,10 @@ def aks_get_versions_table_format(result): table_transformer=aks_get_versions_table_format) cli_command(__name__, 'aks install-cli', 'azure.cli.command_modules.acs.custom#k8s_install_cli') + cli_command(__name__, 'aks install-connector', + 'azure.cli.command_modules.acs.custom#k8s_install_connector', _aks_client_factory) + cli_command(__name__, 'aks remove-connector', + 'azure.cli.command_modules.acs.custom#k8s_uninstall_connector', _aks_client_factory) cli_command(__name__, 'aks list', 'azure.cli.command_modules.acs.custom#aks_list', _aks_client_factory, table_transformer=aks_list_table_format) diff --git a/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/custom.py b/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/custom.py index fbb759b8a5e..f4021661430 100644 --- a/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/custom.py +++ b/src/command_modules/azure-cli-acs/azure/cli/command_modules/acs/custom.py @@ -311,6 +311,96 @@ def k8s_install_cli(client_version='latest', install_location=None): raise CLIError('Connection error while attempting to download client ({})'.format(ex)) +def k8s_install_connector(client, name, resource_group, connector_name, + location=None, service_principal=None, client_secret=None, + chart_url=None, os_type='Linux'): + from subprocess import PIPE, Popen + helm_not_installed = "Helm not detected, please verify if it is installed." + image_tag = 'latest' + url_chart = chart_url + # Check if Helm is installed locally + try: + Popen(["helm"], stdout=PIPE, stderr=PIPE) + except OSError: + raise CLIError(helm_not_installed) + # Validate if the RG exists + groups = _resource_client_factory().resource_groups + # Just do the get, we don't need the result, it will error out if the group doesn't exist. + rgkaci = groups.get(resource_group) + # Auto assign the location + if location is None: + location = rgkaci.location # pylint:disable=no-member + # Get the credentials from a AKS instance + _, browse_path = tempfile.mkstemp() + aks_get_credentials(client, resource_group, name, admin=False, path=browse_path) + subscription_id = _get_subscription_id() + dns_name_prefix = _get_default_dns_prefix(connector_name, resource_group, subscription_id) + # Ensure that the SPN exists + principal_obj = _ensure_service_principal(service_principal, client_secret, subscription_id, + dns_name_prefix, location, connector_name) + client_secret = principal_obj.get("client_secret") + service_principal = principal_obj.get("service_principal") + # Get the TenantID + profile = Profile() + _, _, tenant_id = profile.get_login_credentials() + # Check if we want the windows connector + if os_type.lower() in ['windows', 'both']: + # The current connector will deploy two connectors, one for Windows and another one for Linux + image_tag = 'canary' + logger.warning('Deploying the aci-connector using Helm') + try: + subprocess.call(["helm", "install", url_chart, "--name", connector_name, "--set", "env.azureClientId=" + + service_principal + ",env.azureClientKey=" + client_secret + ",env.azureSubscriptionId=" + + subscription_id + ",env.azureTenantId=" + tenant_id + ",env.aciResourceGroup=" + rgkaci.name + + ",env.aciRegion=" + location + ",image.tag=" + image_tag]) + except subprocess.CalledProcessError as err: + raise CLIError('Could not deploy the ACI Chart: {}'.format(err)) + + +def k8s_uninstall_connector(client, name, connector_name, resource_group, + graceful=False, os_type='Linux'): + from subprocess import PIPE, Popen + helm_not_installed = "Error : Helm not detected, please verify if it is installed." + # Check if Helm is installed locally + try: + Popen(["helm"], stdout=PIPE, stderr=PIPE) + except OSError: + raise CLIError(helm_not_installed) + # Get the credentials from a AKS instance + _, browse_path = tempfile.mkstemp() + aks_get_credentials(client, resource_group, name, admin=False, path=browse_path) + if graceful: + logger.warning('Graceful option selected, will try to drain the node first') + kubectl_not_installed = "Kubectl not detected, please verify if it is installed." + try: + Popen(["kubectl"], stdout=PIPE, stderr=PIPE) + except OSError: + raise CLIError(kubectl_not_installed) + try: + if os_type.lower() in ['windows', 'both']: + nodes = ['-0', '-1'] + for n in nodes: + drain_node = subprocess.check_output( + ["kubectl", "drain", "aci-connector{}".format(n), "--force"], + universal_newlines=True) + else: + drain_node = subprocess.check_output( + ["kubectl", "drain", "aci-connector", "--force"], + universal_newlines=True) + except subprocess.CalledProcessError as err: + raise CLIError('Could not find the node, make sure you' + + ' are using the correct --os-type option: {}'.format(err)) + if not drain_node: + raise CLIError('Could not find the node, make sure you' + + ' are using the correct --os-type') + + logger.warning('Undeploying the aci-connector using Helm') + try: + subprocess.call(["helm", "del", connector_name, "--purge"]) + except subprocess.CalledProcessError as err: + raise CLIError('Could not deploy the ACI Chart: {}'.format(err)) + + def _build_service_principal(client, name, url, client_secret): # use get_progress_controller hook = APPLICATION.get_progress_controller(True) diff --git a/src/command_modules/azure-cli-acs/setup.py b/src/command_modules/azure-cli-acs/setup.py index 245fba1edb4..1cf443c2117 100644 --- a/src/command_modules/azure-cli-acs/setup.py +++ b/src/command_modules/azure-cli-acs/setup.py @@ -14,7 +14,7 @@ logger.warn("Wheel is not available, disabling bdist_wheel hook") cmdclass = {} -VERSION = "2.0.20" +VERSION = "2.0.21" CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers',