Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dapr] Prompt user for existing Dapr installation during extension create #188

Merged
128 changes: 114 additions & 14 deletions src/k8s-extension/azext_k8s_extension/partner_extensions/Dapr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,143 @@
# --------------------------------------------------------------------------------------------

# pylint: disable=unused-argument
# pylint: disable=line-too-long
# pylint: disable=too-many-locals
# pylint: disable=too-many-instance-attributes

from typing import Tuple

from azure.cli.core.azclierror import InvalidArgumentValueError
from knack.log import get_logger
from knack.prompting import prompt, prompt_y_n

from ..vendored_sdks.models import Extension, Scope, ScopeCluster
from .DefaultExtension import DefaultExtension

from ..vendored_sdks.models import (
Extension,
)
logger = get_logger(__name__)


class Dapr(DefaultExtension):
def __init__(self):
self.TSG_LINK = "https://docs.microsoft.com/en-us/azure/aks/dapr"
self.DEFAULT_RELEASE_NAME = 'dapr'
self.DEFAULT_RELEASE_NAMESPACE = 'dapr-system'
self.DEFAULT_RELEASE_TRAIN = 'stable'
self.DEFAULT_CLUSTER_TYPE = 'managedclusters'
self.DEFAULT_HA = 'true'

# constants for configuration settings.
self.CLUSTER_TYPE = 'global.clusterType'
self.CLUSTER_TYPE_KEY = 'global.clusterType'
self.HA_KEY_ENABLED_KEY = 'global.ha.enabled'
self.SKIP_EXISTING_DAPR_CHECK_KEY = 'skipExistingDaprCheck'
self.EXISTING_DAPR_RELEASE_NAME_KEY = 'existingDaprReleaseName'
self.EXISTING_DAPR_RELEASE_NAMESPACE_KEY = 'existingDaprReleaseNamespace'

# constants for message prompts.
self.MSG_IS_DAPR_INSTALLED = "Is Dapr already installed in the cluster?"
self.MSG_ENTER_RELEASE_NAME = "Enter the Helm release name for Dapr, "\
f"or press Enter to use the default name [{self.DEFAULT_RELEASE_NAME}]: "
self.MSG_ENTER_RELEASE_NAMESPACE = "Enter the namespace where Dapr is installed, "\
f"or press Enter to use the default namespace [{self.DEFAULT_RELEASE_NAMESPACE}]: "
self.MSG_WARN_EXISTING_INSTALLATION = "The extension will use your existing Dapr installation. "\
f"Note, if you have updated the default values for global.ha.* or dapr_placement.* in your existing "\
"Dapr installation, you must provide them via --configuration-settings. Failing to do so will result in"\
"an error, since Helm upgrade will try to modify the StatefulSet."\
f"Please refer to {self.TSG_LINK} for more information."

self.RELEASE_INFO_HELP_STRING = "The Helm release name and namespace can be found by running 'helm list -A'."

# constants for error messages.
self.ERR_MSG_INVALID_SCOPE_TPL = "Invalid scope '{}'. This extension can be installed only at 'cluster' scope."\
shubham1172 marked this conversation as resolved.
Show resolved Hide resolved
f" Check {self.TSG_LINK} for more information."

def _get_release_info(self, release_name: str, release_namespace: str, configuration_settings: dict)\
-> Tuple[str, str, bool]:
'''
Check if Dapr is already installed in the cluster and get the release name and namespace.
If user has provided the release name and namespace in configuration settings, use those.
Otherwise, prompt the user for the release name and namespace.
If Dapr is not installed, return the default release name and namespace.
'''
name, namespace, dapr_exists = release_name, release_namespace, False

# Set the default release name and namespace if not provided.
name = name or self.DEFAULT_RELEASE_NAME
namespace = namespace or self.DEFAULT_RELEASE_NAMESPACE

if configuration_settings.get(self.SKIP_EXISTING_DAPR_CHECK_KEY, 'false') == 'true':
shubham1172 marked this conversation as resolved.
Show resolved Hide resolved
logger.info("%s is set to true, skipping existing Dapr check.", self.SKIP_EXISTING_DAPR_CHECK_KEY)
return name, namespace, False
shubham1172 marked this conversation as resolved.
Show resolved Hide resolved

cfg_name = configuration_settings.get(self.EXISTING_DAPR_RELEASE_NAME_KEY, None)
cfg_namespace = configuration_settings.get(self.EXISTING_DAPR_RELEASE_NAMESPACE_KEY, None)

def Create(self, cmd, client, resource_group_name, cluster_name, name, cluster_type, cluster_rp,
extension_type, scope, auto_upgrade_minor_version, release_train, version, target_namespace,
release_namespace, configuration_settings, configuration_protected_settings,
configuration_settings_file, configuration_protected_settings_file):
# If the user has specified the release name and namespace in configuration settings, then use it.
if cfg_name and cfg_namespace:
logger.info("Using the release name and namespace specified in the configuration settings.")
return cfg_name, cfg_namespace, True

# If either release name or namespace is missing, ignore the configuration settings and prompt the user.
if cfg_name or cfg_namespace:
NarayanThiru marked this conversation as resolved.
Show resolved Hide resolved
logger.warning("Both '%s' and '%s' must be specified via --configuration-settings. Only one of them is "
"specified, ignoring.", self.EXISTING_DAPR_RELEASE_NAME_KEY,
self.EXISTING_DAPR_RELEASE_NAMESPACE_KEY)
shubham1172 marked this conversation as resolved.
Show resolved Hide resolved

# Check explictly if Dapr is already installed in the cluster.
# If so, reuse the release name and namespace to avoid conflicts.
if prompt_y_n(self.MSG_IS_DAPR_INSTALLED, default='n'):
dapr_exists = True

name = prompt(self.MSG_ENTER_RELEASE_NAME, self.RELEASE_INFO_HELP_STRING) or self.DEFAULT_RELEASE_NAME
if release_name and name != release_name:
logger.warning("The release name has been changed from '%s' to '%s'.", release_name, name)

namespace = prompt(self.MSG_ENTER_RELEASE_NAMESPACE, self.RELEASE_INFO_HELP_STRING)\
or self.DEFAULT_RELEASE_NAMESPACE
if release_namespace and namespace != release_namespace:
logger.warning("The release namespace has been changed from '%s' to '%s'.",
release_namespace, namespace)

return name, namespace, dapr_exists

def Create(self, cmd, client, resource_group_name: str, cluster_name: str, name: str, cluster_type: str,
cluster_rp: str, extension_type: str, scope: str, auto_upgrade_minor_version: bool,
release_train: str, version: str, target_namespace: str, release_namespace: str,
configuration_settings: dict, configuration_protected_settings: dict,
configuration_settings_file: str, configuration_protected_settings_file: str):
"""ExtensionType 'Microsoft.Dapr' specific validations & defaults for Create
Must create and return a valid 'ExtensionInstance' object.
"""

if cluster_type.lower() == '' or cluster_type.lower() == 'managedclusters':
configuration_settings[self.CLUSTER_TYPE] = 'managedclusters'
# Dapr extension is only supported at the cluster scope.
if scope == 'namespace':
raise InvalidArgumentValueError(self.ERR_MSG_INVALID_SCOPE_TPL.format(scope))

release_name, release_namespace, dapr_exists = \
self._get_release_info(name, release_namespace, configuration_settings)

# Inform the user that the extension will be installed on an existing Dapr installation.
# Disable HA mode if Dapr is already installed in the cluster.
if dapr_exists:
shubham1172 marked this conversation as resolved.
Show resolved Hide resolved
logger.warning(self.MSG_WARN_EXISTING_INSTALLATION)
if self.HA_KEY_ENABLED_KEY not in configuration_settings:
configuration_settings[self.HA_KEY_ENABLED_KEY] = 'false'

scope_cluster = ScopeCluster(release_namespace=release_namespace or self.DEFAULT_RELEASE_NAMESPACE)
extension_scope = Scope(cluster=scope_cluster, namespace=None)

if cluster_type.lower() == '' or cluster_type.lower() == self.DEFAULT_CLUSTER_TYPE:
configuration_settings[self.CLUSTER_TYPE_KEY] = self.DEFAULT_CLUSTER_TYPE

create_identity = False
extension_instance = Extension(
extension_type=extension_type,
auto_upgrade_minor_version=auto_upgrade_minor_version,
release_train=release_train,
release_train=release_train or self.DEFAULT_RELEASE_TRAIN,
version=version,
scope=scope,
scope=extension_scope,
configuration_settings=configuration_settings,
configuration_protected_settings=configuration_protected_settings,
identity=None,
location=""
)
return extension_instance, name, create_identity
return extension_instance, release_name, create_identity
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def test_k8s_extension(self):

self.cmd('k8s-extension create -g {rg} -n {name} -c {cluster_name} --cluster-type {cluster_type} '
'--extension-type {extension_type} --release-train {release_train} --version {version} '
'--no-wait')
'--configuration-settings "skipExistingDaprCheck=true" --no-wait')

# Update requires agent running in k8s cluster that is connected to Azure - so no update tests here
# self.cmd('k8s-extension update -g {rg} -n {name} --tags foo=boo', checks=[
Expand Down
63 changes: 63 additions & 0 deletions testing/test/extensions/public/Dapr.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
Describe 'DAPR Testing' {
BeforeAll {
$extensionType = "microsoft.dapr"
$extensionName = "dapr"
$clusterType = "connectedClusters"

. $PSScriptRoot/../../helper/Constants.ps1
. $PSScriptRoot/../../helper/Helper.ps1
}

It 'Creates the extension and checks that it onboards correctly' {
$output = az $Env:K8sExtensionName create -c $($ENVCONFIG.arcClusterName) -g $($ENVCONFIG.resourceGroup) --cluster-type $clusterType -n $extensionName --extension-type $extensionType --configuration-settings "skipExistingDaprCheck=true" --no-wait
$? | Should -BeTrue

$n = 0
do
{
$output = az $Env:K8sExtensionName show -c $($ENVCONFIG.arcClusterName) -g $($ENVCONFIG.resourceGroup) --cluster-type $clusterType -n $extensionName
$provisioningState = ($output | ConvertFrom-Json).provisioningState
Write-Host "Provisioning State: $provisioningState"
if ($provisioningState -eq "Succeeded") {
break
}
Start-Sleep -Seconds 40
$n += 1
} while ($n -le $MAX_RETRY_ATTEMPTS)
$n | Should -BeLessOrEqual $MAX_RETRY_ATTEMPTS
}

It "Performs a show on the extension" {
$output = az $Env:K8sExtensionName show -c $($ENVCONFIG.arcClusterName) -g $($ENVCONFIG.resourceGroup) --cluster-type $clusterType -n $extensionName
$? | Should -BeTrue
$output | Should -Not -BeNullOrEmpty
}

It "Lists the extensions on the cluster" {
$output = az $Env:K8sExtensionName list -c $($ENVCONFIG.arcClusterName) -g $($ENVCONFIG.resourceGroup) --cluster-type $clusterType
$? | Should -BeTrue

$output | Should -Not -BeNullOrEmpty
$extensionExists = $output | ConvertFrom-Json | Where-Object { $_.extensionType -eq $extensionType }
$extensionExists | Should -Not -BeNullOrEmpty
}

It "Deletes the extension from the cluster" {
$output = az $Env:K8sExtensionName delete -c $($ENVCONFIG.arcClusterName) -g $($ENVCONFIG.resourceGroup) --cluster-type $clusterType -n $extensionName --force
$? | Should -BeTrue

# Extension should not be found on the cluster
$output = az $Env:K8sExtensionName show -c $($ENVCONFIG.arcClusterName) -g $($ENVCONFIG.resourceGroup) --cluster-type $clusterType -n $extensionName
$? | Should -BeFalse
$output | Should -BeNullOrEmpty
}

It "Performs another list after the delete" {
$output = az $Env:K8sExtensionName list -c $($ENVCONFIG.arcClusterName) -g $($ENVCONFIG.resourceGroup) --cluster-type $clusterType
$? | Should -BeTrue
$output | Should -Not -BeNullOrEmpty

$extensionExists = $output | ConvertFrom-Json | Where-Object { $_.extensionType -eq $extensionName }
$extensionExists | Should -BeNullOrEmpty
}
}