Skip to content

Commit 6ee229c

Browse files
authored
K8s extension/release 1.3.6 (#5424)
1 parent 7bad51b commit 6ee229c

File tree

9 files changed

+556
-14
lines changed

9 files changed

+556
-14
lines changed

src/k8s-extension/HISTORY.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
33
Release History
44
===============
5+
6+
1.3.6
7+
++++++++++++++++++
8+
* Update the api version and add tests for extension type calls
9+
* Fix the TypeError: cf_k8s_extension() takes 1 positional argument but 2 were given while running all az k8s-extension extension-types commands
10+
* microsoft.azuremonitor.containers: Update DCR creation to Clusters resource group instead of workspace
11+
* microsoft.dataprotection.kubernetes: Authoring a new k8s partner extension for the BCDR solution of AKS clusters
12+
513
1.3.5
614
++++++++++++++++++
715
* Use the api-version 2022-04-02-preview in the CLI command az k8s-extension extension-types list

src/k8s-extension/azext_k8s_extension/_client_factory.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,24 @@ def cf_k8s_extension(cli_ctx, **kwargs):
1313
return get_mgmt_service_client(cli_ctx, SourceControlConfigurationClient, **kwargs)
1414

1515

16-
def cf_k8s_extension_operation(cli_ctx, _):
16+
def cf_k8s_extension_operation(cli_ctx, *_):
1717
return cf_k8s_extension(cli_ctx).extensions
1818

1919

20-
def cf_k8s_cluster_extension_types_operation(cli_ctx, _):
21-
return cf_k8s_extension(cli_ctx).cluster_extension_types
20+
def cf_k8s_cluster_extension_types_operation(cli_ctx, *_):
21+
return cf_k8s_extension(cli_ctx, api_version=consts.EXTENSION_TYPE_API_VERSION).cluster_extension_types
2222

2323

24-
def cf_k8s_cluster_extension_type_operation(cli_ctx, _):
25-
return cf_k8s_extension(cli_ctx, consts.EXTENSION_TYPE_API_VERSION).cluster_extension_type
24+
def cf_k8s_cluster_extension_type_operation(cli_ctx, *_):
25+
return cf_k8s_extension(cli_ctx, api_version=consts.EXTENSION_TYPE_API_VERSION).cluster_extension_type
2626

2727

28-
def cf_k8s_location_extension_types_operation(cli_ctx, _):
29-
return cf_k8s_extension(cli_ctx, consts.EXTENSION_TYPE_API_VERSION).location_extension_types
28+
def cf_k8s_location_extension_types_operation(cli_ctx, *_):
29+
return cf_k8s_extension(cli_ctx, api_version=consts.EXTENSION_TYPE_API_VERSION).location_extension_types
3030

3131

32-
def cf_k8s_extension_type_versions_operation(cli_ctx, _):
33-
return cf_k8s_extension(cli_ctx, consts.EXTENSION_TYPE_API_VERSION).extension_type_versions
32+
def cf_k8s_extension_type_versions_operation(cli_ctx, *_):
33+
return cf_k8s_extension(cli_ctx, api_version=consts.EXTENSION_TYPE_API_VERSION).extension_type_versions
3434

3535

3636
def cf_resource_groups(cli_ctx, subscription_id=None):
@@ -51,3 +51,13 @@ def cf_log_analytics(cli_ctx, subscription_id=None):
5151
def _resource_providers_client(cli_ctx):
5252
from azure.mgmt.resource import ResourceManagementClient
5353
return get_mgmt_service_client(cli_ctx, ResourceManagementClient).providers
54+
55+
56+
def cf_storage(cli_ctx, subscription_id=None):
57+
from azure.mgmt.storage import StorageManagementClient
58+
return get_mgmt_service_client(cli_ctx, StorageManagementClient, subscription_id=subscription_id)
59+
60+
61+
def cf_managed_clusters(cli_ctx, subscription_id=None):
62+
from azure.mgmt.containerservice import ContainerServiceClient
63+
return get_mgmt_service_client(cli_ctx, ContainerServiceClient, subscription_id=subscription_id).managed_clusters

src/k8s-extension/azext_k8s_extension/consts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@
2525
APPLIANCE_API_VERSION = "2021-10-31-preview"
2626
HYBRIDCONTAINERSERVICE_API_VERSION = "2022-05-01-preview"
2727

28-
EXTENSION_TYPE_API_VERSION = "2022-04-02-preview"
28+
EXTENSION_TYPE_API_VERSION = "2022-01-15-preview"

src/k8s-extension/azext_k8s_extension/custom.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from .partner_extensions.AzureDefender import AzureDefender
2828
from .partner_extensions.OpenServiceMesh import OpenServiceMesh
2929
from .partner_extensions.AzureMLKubernetes import AzureMLKubernetes
30+
from .partner_extensions.DataProtectionKubernetes import DataProtectionKubernetes
3031
from .partner_extensions.Dapr import Dapr
3132
from .partner_extensions.DefaultExtension import (
3233
DefaultExtension,
@@ -47,6 +48,7 @@ def ExtensionFactory(extension_name):
4748
"microsoft.openservicemesh": OpenServiceMesh,
4849
"microsoft.azureml.kubernetes": AzureMLKubernetes,
4950
"microsoft.dapr": Dapr,
51+
"microsoft.dataprotection.kubernetes": DataProtectionKubernetes,
5052
}
5153

5254
# Return the extension if we find it in the map, else return the default

src/k8s-extension/azext_k8s_extension/partner_extensions/ContainerInsights.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -587,8 +587,8 @@ def _ensure_container_insights_dcr_for_monitoring(cmd, subscription_id, cluster_
587587
raise ex
588588

589589
# extract subscription ID and resource group from workspace_resource_id URL
590-
parsed = parse_resource_id(workspace_resource_id)
591-
workspace_subscription_id, workspace_resource_group = parsed["subscription"], parsed["resource_group"]
590+
parsed = parse_resource_id(workspace_resource_id.lower())
591+
workspace_subscription_id = parsed["subscription"]
592592
workspace_region = ''
593593
resources = cf_resources(cmd.cli_ctx, workspace_subscription_id)
594594
try:
@@ -601,7 +601,7 @@ def _ensure_container_insights_dcr_for_monitoring(cmd, subscription_id, cluster_
601601
raise ex
602602

603603
dataCollectionRuleName = f"MSCI-{cluster_name}-{cluster_region}"
604-
dcr_resource_id = f"/subscriptions/{workspace_subscription_id}/resourceGroups/{workspace_resource_group}/providers/Microsoft.Insights/dataCollectionRules/{dataCollectionRuleName}"
604+
dcr_resource_id = f"/subscriptions/{subscription_id}/resourceGroups/{cluster_resource_group_name}/providers/Microsoft.Insights/dataCollectionRules/{dataCollectionRuleName}"
605605

606606
# first get the association between region display names and region IDs (because for some reason
607607
# the "which RPs are available in which regions" check returns region display names)
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
# pylint: disable=unused-argument
7+
from knack.log import get_logger
8+
from azure.cli.core.commands.client_factory import get_subscription_id
9+
from azure.cli.core.azclierror import RequiredArgumentMissingError, InvalidArgumentValueError
10+
11+
from .DefaultExtension import DefaultExtension
12+
from .._client_factory import cf_storage, cf_managed_clusters
13+
from ..vendored_sdks.models import (Extension, PatchExtension, Scope, ScopeCluster)
14+
15+
logger = get_logger(__name__)
16+
17+
18+
class DataProtectionKubernetes(DefaultExtension):
19+
def __init__(self):
20+
"""Constants for configuration settings
21+
- Tenant Id (required)
22+
- Backup storage location (required)
23+
- Resource Limits (optional)
24+
"""
25+
self.TENANT_ID = "credentials.tenantId"
26+
self.BACKUP_STORAGE_ACCOUNT_CONTAINER = "configuration.backupStorageLocation.bucket"
27+
self.BACKUP_STORAGE_ACCOUNT_NAME = "configuration.backupStorageLocation.config.storageAccount"
28+
self.BACKUP_STORAGE_ACCOUNT_RESOURCE_GROUP = "configuration.backupStorageLocation.config.resourceGroup"
29+
self.BACKUP_STORAGE_ACCOUNT_SUBSCRIPTION = "configuration.backupStorageLocation.config.subscriptionId"
30+
self.RESOURCE_LIMIT_CPU = "resources.limits.cpu"
31+
self.RESOURCE_LIMIT_MEMORY = "resources.limits.memory"
32+
33+
self.blob_container = "blobContainer"
34+
self.storage_account = "storageAccount"
35+
self.storage_account_resource_group = "storageAccountResourceGroup"
36+
self.storage_account_subsciption = "storageAccountSubscriptionId"
37+
self.cpu_limit = "cpuLimit"
38+
self.memory_limit = "memoryLimit"
39+
40+
self.configuration_mapping = {
41+
self.blob_container.lower(): self.BACKUP_STORAGE_ACCOUNT_CONTAINER,
42+
self.storage_account.lower(): self.BACKUP_STORAGE_ACCOUNT_NAME,
43+
self.storage_account_resource_group.lower(): self.BACKUP_STORAGE_ACCOUNT_RESOURCE_GROUP,
44+
self.storage_account_subsciption.lower(): self.BACKUP_STORAGE_ACCOUNT_SUBSCRIPTION,
45+
self.cpu_limit.lower(): self.RESOURCE_LIMIT_CPU,
46+
self.memory_limit.lower(): self.RESOURCE_LIMIT_MEMORY
47+
}
48+
49+
self.bsl_configuration_settings = [
50+
self.blob_container,
51+
self.storage_account,
52+
self.storage_account_resource_group,
53+
self.storage_account_subsciption
54+
]
55+
56+
def Create(
57+
self,
58+
cmd,
59+
client,
60+
resource_group_name,
61+
cluster_name,
62+
name,
63+
cluster_type,
64+
cluster_rp,
65+
extension_type,
66+
scope,
67+
auto_upgrade_minor_version,
68+
release_train,
69+
version,
70+
target_namespace,
71+
release_namespace,
72+
configuration_settings,
73+
configuration_protected_settings,
74+
configuration_settings_file,
75+
configuration_protected_settings_file
76+
):
77+
# Current scope of DataProtection Kubernetes Backup extension is 'cluster' #TODO: add TSGs when they are in place
78+
if scope == 'namespace':
79+
raise InvalidArgumentValueError(f"Invalid scope '{scope}'. This extension can only be installed at 'cluster' scope.")
80+
81+
scope_cluster = ScopeCluster(release_namespace=release_namespace)
82+
ext_scope = Scope(cluster=scope_cluster, namespace=None)
83+
84+
if cluster_type.lower() != 'managedclusters':
85+
raise InvalidArgumentValueError(f"Invalid cluster type '{cluster_type}'. This extension can only be installed for managed clusters.")
86+
87+
if release_namespace is not None:
88+
logger.warning(f"Ignoring 'release-namespace': {release_namespace}")
89+
90+
tenant_id = self.__get_tenant_id(cmd.cli_ctx)
91+
if not tenant_id:
92+
raise SystemExit(logger.error("Unable to fetch TenantId. Please check your subscription or run 'az login' to login to Azure."))
93+
94+
self.__validate_and_map_config(configuration_settings)
95+
self.__validate_backup_storage_account(cmd.cli_ctx, resource_group_name, cluster_name, configuration_settings)
96+
97+
configuration_settings[self.TENANT_ID] = tenant_id
98+
99+
if release_train is None:
100+
release_train = 'stable'
101+
102+
create_identity = True
103+
extension = Extension(
104+
extension_type=extension_type,
105+
auto_upgrade_minor_version=True,
106+
release_train=release_train,
107+
scope=ext_scope,
108+
configuration_settings=configuration_settings
109+
)
110+
return extension, name, create_identity
111+
112+
def Update(
113+
self,
114+
cmd,
115+
resource_group_name,
116+
cluster_name,
117+
auto_upgrade_minor_version,
118+
release_train,
119+
version,
120+
configuration_settings,
121+
configuration_protected_settings,
122+
original_extension,
123+
yes=False,
124+
):
125+
if configuration_settings is None:
126+
configuration_settings = {}
127+
128+
if len(configuration_settings) > 0:
129+
bsl_specified = self.__is_bsl_specified(configuration_settings)
130+
self.__validate_and_map_config(configuration_settings, validate_bsl=bsl_specified)
131+
if bsl_specified:
132+
self.__validate_backup_storage_account(cmd.cli_ctx, resource_group_name, cluster_name, configuration_settings)
133+
134+
return PatchExtension(
135+
auto_upgrade_minor_version=True,
136+
release_train=release_train,
137+
configuration_settings=configuration_settings,
138+
)
139+
140+
def __get_tenant_id(self, cli_ctx):
141+
from azure.cli.core._profile import Profile
142+
if not cli_ctx.data.get('tenant_id'):
143+
cli_ctx.data['tenant_id'] = Profile(cli_ctx=cli_ctx).get_subscription()['tenantId']
144+
return cli_ctx.data['tenant_id']
145+
146+
def __validate_and_map_config(self, configuration_settings, validate_bsl=True):
147+
"""Validate and set configuration settings for Data Protection K8sBackup extension"""
148+
input_configuration_settings = dict(configuration_settings.items())
149+
input_configuration_keys = [key.lower() for key in configuration_settings]
150+
151+
if validate_bsl:
152+
for key in self.bsl_configuration_settings:
153+
if key.lower() not in input_configuration_keys:
154+
raise RequiredArgumentMissingError(f"Missing required configuration setting: {key}")
155+
156+
for key in input_configuration_settings:
157+
_key = key.lower()
158+
if _key in self.configuration_mapping:
159+
configuration_settings[self.configuration_mapping[_key]] = configuration_settings.pop(key)
160+
else:
161+
configuration_settings.pop(key)
162+
logger.warning(f"Ignoring unrecognized configuration setting: {key}")
163+
164+
def __validate_backup_storage_account(self, cli_ctx, resource_group_name, cluster_name, configuration_settings):
165+
"""Validations performed on the backup storage account
166+
- Existance of the storage account
167+
- Cluster and storage account are in the same location
168+
"""
169+
sa_subscription_id = configuration_settings[self.BACKUP_STORAGE_ACCOUNT_SUBSCRIPTION]
170+
storage_account_client = cf_storage(cli_ctx, sa_subscription_id).storage_accounts
171+
172+
storage_account = storage_account_client.get_properties(
173+
configuration_settings[self.BACKUP_STORAGE_ACCOUNT_RESOURCE_GROUP],
174+
configuration_settings[self.BACKUP_STORAGE_ACCOUNT_NAME])
175+
176+
cluster_subscription_id = get_subscription_id(cli_ctx)
177+
managed_clusters_client = cf_managed_clusters(cli_ctx, cluster_subscription_id)
178+
managed_cluster = managed_clusters_client.get(
179+
resource_group_name,
180+
cluster_name)
181+
182+
if managed_cluster.location != storage_account.location:
183+
error_message = f"The Kubernetes managed cluster '{cluster_name} ({managed_cluster.location})' and the backup storage account '{configuration_settings[self.BACKUP_STORAGE_ACCOUNT_NAME]} ({storage_account.location})' are not in the same location. Please make sure that the cluster and the storage account are in the same location."
184+
raise SystemExit(logger.error(error_message))
185+
186+
def __is_bsl_specified(self, configuration_settings):
187+
"""Check if the backup storage account is specified in the input"""
188+
input_configuration_keys = [key.lower() for key in configuration_settings]
189+
for key in self.bsl_configuration_settings:
190+
if key.lower() in input_configuration_keys:
191+
return True
192+
return False

src/k8s-extension/azext_k8s_extension/tests/latest/recordings/test_k8s_extension_types.yaml

Lines changed: 290 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
# pylint: disable=line-too-long
7+
8+
import os
9+
from azure.cli.testsdk import (ScenarioTest, record_only)
10+
11+
12+
TEST_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), '..'))
13+
14+
15+
class K8sExtensionTypesScenarioTest(ScenarioTest):
16+
@record_only()
17+
def test_k8s_extension_types(self):
18+
extension_type = 'cassandradatacentersoperator'
19+
self.kwargs.update({
20+
'rg': 'clitest-rg', #K8sPartnerExtensionTest',
21+
'cluster_name': 'kind-clitest-cluster',#'k8s-extension-cluster-32469-arc',
22+
'cluster_type': 'connectedClusters',
23+
'extension_type': extension_type,
24+
'location': 'eastus2euap'
25+
})
26+
27+
self.cmd('k8s-extension extension-types show -g {rg} -c {cluster_name} --cluster-type {cluster_type} '
28+
'--extension-type {extension_type}', checks=[
29+
self.check('name', '{extension_type}')
30+
])
31+
32+
extensionTypes_list = self.cmd('k8s-extension extension-types list -g {rg} -c {cluster_name} '
33+
'--cluster-type {cluster_type}').get_output_in_json()
34+
assert len(extensionTypes_list) > 0
35+
36+
extensionTypes_locationList = self.cmd('k8s-extension extension-types list-by-location --location '
37+
'{location}').get_output_in_json()
38+
assert len(extensionTypes_locationList) > 0
39+
40+
self.cmd('k8s-extension extension-types list-versions --location {location} --extension-type {extension_type}')

src/k8s-extension/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
# TODO: Add any additional SDK dependencies here
3434
DEPENDENCIES = []
3535

36-
VERSION = "1.3.5"
36+
VERSION = "1.3.6"
3737

3838
with open("README.rst", "r", encoding="utf-8") as f:
3939
README = f.read()

0 commit comments

Comments
 (0)