From 8f3b4eda3efefd026b8c893b2b9c8ed0201f3bd7 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Wed, 23 Apr 2025 17:40:57 +0200 Subject: [PATCH 01/40] initial working version --- .github/CODEOWNERS | 4 +- src/zones/HISTORY.rst | 8 + src/zones/README.rst | 5 + src/zones/azext_zones/__init__.py | 41 + src/zones/azext_zones/_argHelper.py | 126 ++ src/zones/azext_zones/_client_factory.py | 9 + src/zones/azext_zones/_clients.py | 29 + src/zones/azext_zones/_help.py | 18 + src/zones/azext_zones/_params.py | 11 + src/zones/azext_zones/_validators.py | 4 + src/zones/azext_zones/azext_metadata.json | 4 + src/zones/azext_zones/commands.py | 16 + src/zones/azext_zones/custom.py | 47 + .../azext_zones/resourceTypeValidation.py | 46 + .../microsoft_storage.py | 18 + src/zones/azext_zones/tests/__init__.py | 5 + .../azext_zones/tests/latest/__init__.py | 5 + .../tests/latest/test_zones_scenario.py | 40 + .../vendored_sdks/resourcegraph/__init__.py | 19 + .../resourcegraph/_configuration.py | 70 + .../resourcegraph/_metadata.json | 110 ++ .../resourcegraph/_resource_graph_client.py | 74 + .../vendored_sdks/resourcegraph/_version.py | 9 + .../resourcegraph/aio/__init__.py | 10 + .../resourcegraph/aio/_configuration.py | 66 + .../aio/_resource_graph_client.py | 68 + .../resourcegraph/aio/operations/__init__.py | 17 + .../aio/operations/_graph_query_operations.py | 362 +++++ .../aio/operations/_operations.py | 105 ++ .../_resource_graph_client_operations.py | 241 +++ .../resourcegraph/models/__init__.py | 136 ++ .../resourcegraph/models/_models.py | 1239 +++++++++++++++ .../resourcegraph/models/_models_py3.py | 1390 +++++++++++++++++ .../models/_resource_graph_client_enums.py | 92 ++ .../resourcegraph/operations/__init__.py | 17 + .../operations/_graph_query_operations.py | 371 +++++ .../resourcegraph/operations/_operations.py | 110 ++ .../_resource_graph_client_operations.py | 249 +++ .../vendored_sdks/resourcegraph/py.typed | 1 + src/zones/setup.cfg | 0 src/zones/setup.py | 58 + 41 files changed, 5249 insertions(+), 1 deletion(-) create mode 100644 src/zones/HISTORY.rst create mode 100644 src/zones/README.rst create mode 100644 src/zones/azext_zones/__init__.py create mode 100644 src/zones/azext_zones/_argHelper.py create mode 100644 src/zones/azext_zones/_client_factory.py create mode 100644 src/zones/azext_zones/_clients.py create mode 100644 src/zones/azext_zones/_help.py create mode 100644 src/zones/azext_zones/_params.py create mode 100644 src/zones/azext_zones/_validators.py create mode 100644 src/zones/azext_zones/azext_metadata.json create mode 100644 src/zones/azext_zones/commands.py create mode 100644 src/zones/azext_zones/custom.py create mode 100644 src/zones/azext_zones/resourceTypeValidation.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_storage.py create mode 100644 src/zones/azext_zones/tests/__init__.py create mode 100644 src/zones/azext_zones/tests/latest/__init__.py create mode 100644 src/zones/azext_zones/tests/latest/test_zones_scenario.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/__init__.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/_configuration.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/_metadata.json create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/_resource_graph_client.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/_version.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/aio/__init__.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/aio/_configuration.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/aio/_resource_graph_client.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/__init__.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/_graph_query_operations.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/_operations.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/_resource_graph_client_operations.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/models/__init__.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/models/_models.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/models/_models_py3.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/models/_resource_graph_client_enums.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/operations/__init__.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/operations/_graph_query_operations.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/operations/_operations.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/operations/_resource_graph_client_operations.py create mode 100644 src/zones/azext_zones/vendored_sdks/resourcegraph/py.typed create mode 100644 src/zones/setup.cfg create mode 100644 src/zones/setup.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 45030b887fe..a121364a454 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -324,4 +324,6 @@ /src/azext_durabletask/ @RyanLettieri -/src/acat @qinqingxu @Sherylueen @yongxin-ms @wh-alice \ No newline at end of file +/src/acat @qinqingxu @Sherylueen @yongxin-ms @wh-alice + +/src/azext_zones/ @nielsams diff --git a/src/zones/HISTORY.rst b/src/zones/HISTORY.rst new file mode 100644 index 00000000000..8c34bccfff8 --- /dev/null +++ b/src/zones/HISTORY.rst @@ -0,0 +1,8 @@ +.. :changelog: + +Release History +=============== + +0.1.0 +++++++ +* Initial release. \ No newline at end of file diff --git a/src/zones/README.rst b/src/zones/README.rst new file mode 100644 index 00000000000..dcb245c467b --- /dev/null +++ b/src/zones/README.rst @@ -0,0 +1,5 @@ +Microsoft Azure CLI 'zones' Extension +========================================== + +This package is for the 'zones' extension. +i.e. 'az zones' \ No newline at end of file diff --git a/src/zones/azext_zones/__init__.py b/src/zones/azext_zones/__init__.py new file mode 100644 index 00000000000..7fc7b1dbb06 --- /dev/null +++ b/src/zones/azext_zones/__init__.py @@ -0,0 +1,41 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +import importlib +from pathlib import Path +from azure.cli.core import AzCommandsLoader +from azext_zones._help import helps # pylint: disable=unused-import + +# Import all the resource type validator modules dynamically: +validators_dir = Path(__file__).parent / "resource_type_validators" +for file in validators_dir.glob("*.py"): + if file.name != "__init__.py": + module_name = f".resource_type_validators.{file.stem}" + importlib.import_module(module_name, package=__package__) + + +class ZonesCommandsLoader(AzCommandsLoader): + + def __init__(self, cli_ctx=None): + from azure.cli.core.commands import CliCommandType + from azext_zones._client_factory import cf_zones + zones_custom = CliCommandType( + operations_tmpl='azext_zones.custom#{}', + client_factory=cf_zones) + super(ZonesCommandsLoader, self).__init__(cli_ctx=cli_ctx, + custom_command_type=zones_custom) + + def load_command_table(self, args): + from azext_zones.commands import load_command_table + load_command_table(self, args) + return self.command_table + + def load_arguments(self, command): + from azext_zones._params import load_arguments + load_arguments(self, command) + + +COMMAND_LOADER_CLS = ZonesCommandsLoader diff --git a/src/zones/azext_zones/_argHelper.py b/src/zones/azext_zones/_argHelper.py new file mode 100644 index 00000000000..657d08eb753 --- /dev/null +++ b/src/zones/azext_zones/_argHelper.py @@ -0,0 +1,126 @@ +import json +from collections import OrderedDict +from knack.util import todict +from knack.log import get_logger + +from .vendored_sdks.resourcegraph.models import ResultTruncated +from .vendored_sdks.resourcegraph.models import QueryRequest, QueryRequestOptions, QueryResponse, ResultFormat, Error + +from azure.cli.core._profile import Profile +from azure.core.exceptions import HttpResponseError +from azure.cli.core.azclierror import BadRequestError, AzureInternalError + + +__SUBSCRIPTION_LIMIT = 1000 +__MANAGEMENT_GROUP_LIMIT = 10 +__logger = get_logger(__name__) + + +def build_arg_query(resource_groups, tags, attributes): + # type: (list[str], list[str], list[str]) -> str + + query = "Resources" + if resource_groups is not None and len(resource_groups) > 0: + query += " | where resourceGroup in ({0})".format(','.join(f"'{item}'" for item in resource_groups.split(','))) + + # if tags is not None and len(tags) > 0: + # query += " | where tags['{0}'] =~ '{1}'".format(tags[0], tags[1]) + + if attributes is not None and len(attributes) > 0: + query += " | project {0}".format(', '.join(attributes)) + + return query + + +def execute_arg_query(client, graph_query, first, skip, subscriptions, management_groups, allow_partial_scopes, skip_token): + mgs_list = management_groups + if mgs_list is not None and len(mgs_list) > __MANAGEMENT_GROUP_LIMIT: + mgs_list = mgs_list[:__MANAGEMENT_GROUP_LIMIT] + warning_message = "The query included more management groups than allowed. "\ + "Only the first {0} management groups were included for the results. "\ + "To use more than {0} management groups, "\ + "see the docs for examples: "\ + "https://aka.ms/arg-error-toomanysubs".format(__MANAGEMENT_GROUP_LIMIT) + __logger.warning(warning_message) + + subs_list = None + if mgs_list is None: + subs_list = subscriptions or _get_cached_subscriptions() + if subs_list is not None and len(subs_list) > __SUBSCRIPTION_LIMIT: + subs_list = subs_list[:__SUBSCRIPTION_LIMIT] + warning_message = "The query included more subscriptions than allowed. "\ + "Only the first {0} subscriptions were included for the results. "\ + "To use more than {0} subscriptions, "\ + "see the docs for examples: "\ + "https://aka.ms/arg-error-toomanysubs".format(__SUBSCRIPTION_LIMIT) + __logger.warning(warning_message) + + response = None + try: + result_truncated = False + + request_options = QueryRequestOptions( + top=first, + skip=skip, + skip_token=skip_token, + result_format=ResultFormat.object_array, + allow_partial_scopes=allow_partial_scopes + ) + + request = QueryRequest( + query=graph_query, + subscriptions=subs_list, + management_groups=mgs_list, + options=request_options) + response = client.resources(request) # type: QueryResponse + if response.result_truncated == ResultTruncated.true: + result_truncated = True + + if result_truncated and first is not None and len(response.data) < first: + __logger.warning("Unable to paginate the results of the query. " + "Some resources may be missing from the results. " + "To rewrite the query and enable paging, " + "see the docs for an example: https://aka.ms/arg-results-truncated") + + except HttpResponseError as ex: + if ex.model.error.code == 'BadRequest': + raise BadRequestError(json.dumps(_to_dict(ex.model.error), indent=4)) from ex + + raise AzureInternalError(json.dumps(_to_dict(ex.model.error), indent=4)) from ex + + result_dict = dict() + result_dict['data'] = response.data + result_dict['count'] = response.count + result_dict['total_records'] = response.total_records + result_dict['skip_token'] = response.skip_token + + return result_dict + + +def _get_cached_subscriptions(): + # type: () -> list[str] + + cached_subs = Profile().load_cached_subscriptions() + return [sub['id'] for sub in cached_subs] + + +def _to_dict(obj): + if isinstance(obj, Error): + return _to_dict(todict(obj)) + + if isinstance(obj, dict): + result = OrderedDict() + + # Complex objects should be displayed last + sorted_keys = sorted(obj.keys(), key=lambda k: (isinstance(obj[k], dict), isinstance(obj[k], list), k)) + for key in sorted_keys: + if obj[key] is None or obj[key] == [] or obj[key] == {}: + continue + + result[key] = _to_dict(obj[key]) + return result + + if isinstance(obj, list): + return [_to_dict(v) for v in obj] + + return obj diff --git a/src/zones/azext_zones/_client_factory.py b/src/zones/azext_zones/_client_factory.py new file mode 100644 index 00000000000..93e05cf4938 --- /dev/null +++ b/src/zones/azext_zones/_client_factory.py @@ -0,0 +1,9 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +def cf_zones(cli_ctx, _): + from azure.cli.core.commands.client_factory import get_mgmt_service_client + from .vendored_sdks.resourcegraph import ResourceGraphClient + return get_mgmt_service_client(cli_ctx, ResourceGraphClient) diff --git a/src/zones/azext_zones/_clients.py b/src/zones/azext_zones/_clients.py new file mode 100644 index 00000000000..875ec5252ee --- /dev/null +++ b/src/zones/azext_zones/_clients.py @@ -0,0 +1,29 @@ +from azure.cli.core.util import send_raw_request +from azure.cli.core.commands.client_factory import get_subscription_id + + +API_VERSION = "2022-10-01" + + +class ARGClient(): + + @classmethod + def query(cls, cmd, query): + management_hostname = cmd.cli_ctx.cloud.endpoints.management + api_version = API_VERSION + sub_id = get_subscription_id(cmd.cli_ctx) + url_fmt = ("{}/providers/Microsoft.ResourceGraph/resources?api-version={}") + request_url = url_fmt.format( + management_hostname.strip('/'), + api_version) + + requestBody = { + "subscriptions": [sub_id], + "query": query, + "options": { + "resultFormat": "objectArray" + } + } + + r = send_raw_request(cmd.cli_ctx, "POST", request_url, body=requestBody) + return r.json() diff --git a/src/zones/azext_zones/_help.py b/src/zones/azext_zones/_help.py new file mode 100644 index 00000000000..93c84e12844 --- /dev/null +++ b/src/zones/azext_zones/_help.py @@ -0,0 +1,18 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.help_files import helps # pylint: disable=unused-import + + +helps['zones'] = """ + type: group + short-summary: Commands to manage Zoness. +""" + +helps['zones validate'] = """ + type: command + short-summary: Validates zone redundancy status. +""" diff --git a/src/zones/azext_zones/_params.py b/src/zones/azext_zones/_params.py new file mode 100644 index 00000000000..d1ff3d2fc3b --- /dev/null +++ b/src/zones/azext_zones/_params.py @@ -0,0 +1,11 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# pylint: disable=line-too-long + +def load_arguments(self, _): + + with self.argument_context('zones validate') as c: + c.argument('resource_group_names', options_list=['--resource-groups', '-g'], help='Name of the resource groups, comma separated.', required=False) + c.argument('tags', options_list=['--tags'], help='Space-separated tags in "key[=value]" format.', required=False) diff --git a/src/zones/azext_zones/_validators.py b/src/zones/azext_zones/_validators.py new file mode 100644 index 00000000000..34913fb394d --- /dev/null +++ b/src/zones/azext_zones/_validators.py @@ -0,0 +1,4 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/src/zones/azext_zones/azext_metadata.json b/src/zones/azext_zones/azext_metadata.json new file mode 100644 index 00000000000..55c81bf3328 --- /dev/null +++ b/src/zones/azext_zones/azext_metadata.json @@ -0,0 +1,4 @@ +{ + "azext.isPreview": true, + "azext.minCliCoreVersion": "2.0.67" +} \ No newline at end of file diff --git a/src/zones/azext_zones/commands.py b/src/zones/azext_zones/commands.py new file mode 100644 index 00000000000..c8182381931 --- /dev/null +++ b/src/zones/azext_zones/commands.py @@ -0,0 +1,16 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +# pylint: disable=line-too-long +from azext_zones._client_factory import cf_zones + + +def load_command_table(self, _): + + with self.command_group('zones', client_factory=cf_zones) as g: + g.custom_command('validate', 'validate_zones') + + with self.command_group('zones', is_preview=True): + pass diff --git a/src/zones/azext_zones/custom.py b/src/zones/azext_zones/custom.py new file mode 100644 index 00000000000..8144c4b2d55 --- /dev/null +++ b/src/zones/azext_zones/custom.py @@ -0,0 +1,47 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.log import get_logger +from .resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ._argHelper import build_arg_query, execute_arg_query + +__logger = get_logger(__name__) + + +def validate_zones(client, resource_group_names): + # Build the ARG query to retrieve resources + query = build_arg_query(resource_group_names, None, None) + __logger.warning("Query: %s", query) + + # Retrieve the list of resources to validate + resources = execute_arg_query(client, query, 100, 0, None, None, False, None) + + # Run validation on the retrieved resources + validation_results = validate_resources(resources) + + # Present the results to the user + return validation_results + + +def validate_resources(resources): + resource_results = [] + if resources['count'] == 0: + errMsg = ("No resources found, validation could not be run.") + __logger.error(errMsg) + + # Loop through the resources and validate each one + for resource in resources['data']: + resourceProvider = resource['type'].split('/')[0] + validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + zrStatus = ZoneRedundancyValidationResult.Unknown if validator is None else validator.validate(resource) + resource_result = {} + resource_result['name'] = resource['name'] + resource_result['location'] = resource['location'] + resource_result['resourceGroup'] = resource['resourceGroup'] + resource_result['type'] = resource['type'] + resource_result['zoneRedundant'] = ZoneRedundancyValidationResult.to_string(zrStatus) + resource_results.append(resource_result) + + return resource_results diff --git a/src/zones/azext_zones/resourceTypeValidation.py b/src/zones/azext_zones/resourceTypeValidation.py new file mode 100644 index 00000000000..31a5de10182 --- /dev/null +++ b/src/zones/azext_zones/resourceTypeValidation.py @@ -0,0 +1,46 @@ + +from abc import ABC, abstractmethod +from enum import Enum + +resource_type_validators = {} + + +# This is the decorator to register resource type validators: +def register_resource_type(resourceType): + def decorator(cls): + resource_type_validators[resourceType] = cls + return cls + return decorator + + +# This is the factory class to get the appropriate validator based on resource type: +class ResourceTypeValidatorFactory: + def getValidator(resourceType): + validator_class = resource_type_validators.get(resourceType) + if validator_class: + return validator_class() + return None + + +# This is the base class for all resource type validators: +class ResourceTypeValidator(ABC): + @abstractmethod + def validate(self): + pass + + +class ZoneRedundancyValidationResult(Enum): + Unknown = 1 # Unable to verify status + Yes = 2 # Resource is configured for zone redundancy + Always = 3 # Resource is always zone redundant + No = 4 # Resource is not configured for zone redundancy + Never = 5 # Resource cannot be configured for zone redundancy + Dependent = 6 # Resource is zone redundant if parent or related resource is zone redundant + + + def to_string(value): + try: + result = ZoneRedundancyValidationResult(value) + return result.name + except ValueError: + return "Unknown" diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py new file mode 100644 index 00000000000..1af0b6cfca2 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py @@ -0,0 +1,18 @@ +from ..resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +# from knack.log import get_logger +# __logger = get_logger(__name__) + +@register_resource_type('microsoft.storage') +class microsoft_storage_storageAccount: + + @staticmethod + def validate(resource): + resourceSubType = resource['type'].split('/')[1] + + resourceTypes = { + 'storageaccounts': + # Storage accounts are zone redundant if they are in the ZRS SKU + ZoneRedundancyValidationResult.Yes if resource['sku']['name'] == 'Standard_ZRS' else ZoneRedundancyValidationResult.No + } + + return resourceTypes.get(resourceSubType, ZoneRedundancyValidationResult.Unknown) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/__init__.py b/src/zones/azext_zones/tests/__init__.py new file mode 100644 index 00000000000..2dcf9bb68b3 --- /dev/null +++ b/src/zones/azext_zones/tests/__init__.py @@ -0,0 +1,5 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# ----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/__init__.py b/src/zones/azext_zones/tests/latest/__init__.py new file mode 100644 index 00000000000..2dcf9bb68b3 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/__init__.py @@ -0,0 +1,5 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# ----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_zones_scenario.py b/src/zones/azext_zones/tests/latest/test_zones_scenario.py new file mode 100644 index 00000000000..775d74aae6a --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_zones_scenario.py @@ -0,0 +1,40 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure_devtools.scenario_tests import AllowLargeResponse +from azure.cli.testsdk import (ScenarioTest, ResourceGroupPreparer) + + +TEST_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), '..')) + + +class ZonesScenarioTest(ScenarioTest): + + @ResourceGroupPreparer(name_prefix='cli_test_zones') + def test_zones(self, resource_group): + + self.kwargs.update({ + 'name': 'test1' + }) + + self.cmd('zones create -g {rg} -n {name} --tags foo=doo', checks=[ + self.check('tags.foo', 'doo'), + self.check('name', '{name}') + ]) + self.cmd('zones update -g {rg} -n {name} --tags foo=boo', checks=[ + self.check('tags.foo', 'boo') + ]) + count = len(self.cmd('zones list').get_output_in_json()) + self.cmd('zones show - {rg} -n {name}', checks=[ + self.check('name', '{name}'), + self.check('resourceGroup', '{rg}'), + self.check('tags.foo', 'boo') + ]) + self.cmd('zones delete -g {rg} -n {name}') + final_count = len(self.cmd('zones list').get_output_in_json()) + self.assertTrue(final_count, count - 1) \ No newline at end of file diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/__init__.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/__init__.py new file mode 100644 index 00000000000..7df07d41873 --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/__init__.py @@ -0,0 +1,19 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from ._resource_graph_client import ResourceGraphClient +from ._version import VERSION + +__version__ = VERSION +__all__ = ['ResourceGraphClient'] + +try: + from ._patch import patch_sdk # type: ignore + patch_sdk() +except ImportError: + pass diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/_configuration.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/_configuration.py new file mode 100644 index 00000000000..c883e46cc0e --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/_configuration.py @@ -0,0 +1,70 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from typing import TYPE_CHECKING + +from azure.core.configuration import Configuration +from azure.core.pipeline import policies +from azure.mgmt.core.policies import ARMHttpLoggingPolicy + +from ._version import VERSION + +if TYPE_CHECKING: + # pylint: disable=unused-import,ungrouped-imports + from typing import Any + + from azure.core.credentials import TokenCredential + + +class ResourceGraphClientConfiguration(Configuration): + """Configuration for ResourceGraphClient. + + Note that all parameters used to create this instance are saved as instance + attributes. + + :param credential: Credential needed for the client to connect to Azure. + :type credential: ~azure.core.credentials.TokenCredential + :param subscription_id: The Azure subscription Id. + :type subscription_id: str + """ + + def __init__( + self, + credential, # type: "TokenCredential" + subscription_id, # type: str + **kwargs # type: Any + ): + # type: (...) -> None + if credential is None: + raise ValueError("Parameter 'credential' must not be None.") + if subscription_id is None: + raise ValueError("Parameter 'subscription_id' must not be None.") + super(ResourceGraphClientConfiguration, self).__init__(**kwargs) + + self.credential = credential + self.subscription_id = subscription_id + self.credential_scopes = kwargs.pop('credential_scopes', ['https://management.azure.com/.default']) + kwargs.setdefault('sdk_moniker', 'mgmt-resourcegraph/{}'.format(VERSION)) + self._configure(**kwargs) + + def _configure( + self, + **kwargs # type: Any + ): + # type: (...) -> None + self.user_agent_policy = kwargs.get('user_agent_policy') or policies.UserAgentPolicy(**kwargs) + self.headers_policy = kwargs.get('headers_policy') or policies.HeadersPolicy(**kwargs) + self.proxy_policy = kwargs.get('proxy_policy') or policies.ProxyPolicy(**kwargs) + self.logging_policy = kwargs.get('logging_policy') or policies.NetworkTraceLoggingPolicy(**kwargs) + self.http_logging_policy = kwargs.get('http_logging_policy') or ARMHttpLoggingPolicy(**kwargs) + self.retry_policy = kwargs.get('retry_policy') or policies.RetryPolicy(**kwargs) + self.custom_hook_policy = kwargs.get('custom_hook_policy') or policies.CustomHookPolicy(**kwargs) + self.redirect_policy = kwargs.get('redirect_policy') or policies.RedirectPolicy(**kwargs) + self.authentication_policy = kwargs.get('authentication_policy') + if self.credential and not self.authentication_policy: + self.authentication_policy = policies.BearerTokenCredentialPolicy(self.credential, *self.credential_scopes, **kwargs) diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/_metadata.json b/src/zones/azext_zones/vendored_sdks/resourcegraph/_metadata.json new file mode 100644 index 00000000000..6b4eca7d986 --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/_metadata.json @@ -0,0 +1,110 @@ +{ + "chosen_version": "", + "total_api_version_list": ["2018-09-01-preview", "2020-04-01-preview", "2020-09-01-preview", "2021-03-01"], + "client": { + "name": "ResourceGraphClient", + "filename": "_resource_graph_client", + "description": "Azure Resource Graph API Reference.", + "base_url": "\u0027https://management.azure.com\u0027", + "custom_base_url": null, + "azure_arm": true, + "has_lro_operations": false, + "client_side_validation": true + }, + "global_parameters": { + "sync": { + "credential": { + "signature": "credential, # type: \"TokenCredential\"", + "description": "Credential needed for the client to connect to Azure.", + "docstring_type": "~azure.core.credentials.TokenCredential", + "required": true + }, + "subscription_id": { + "signature": "subscription_id, # type: str", + "description": "The Azure subscription Id.", + "docstring_type": "str", + "required": true + } + }, + "async": { + "credential": { + "signature": "credential, # type: \"AsyncTokenCredential\"", + "description": "Credential needed for the client to connect to Azure.", + "docstring_type": "~azure.core.credentials_async.AsyncTokenCredential", + "required": true + }, + "subscription_id": { + "signature": "subscription_id, # type: str", + "description": "The Azure subscription Id.", + "docstring_type": "str", + "required": true + } + }, + "constant": { + }, + "call": "credential, subscription_id" + }, + "config": { + "credential": true, + "credential_scopes": ["https://management.azure.com/.default"], + "credential_default_policy_type": "BearerTokenCredentialPolicy", + "credential_default_policy_type_has_async_version": true, + "credential_key_header_name": null + }, + "operation_groups": { + "operations": "Operations", + "graph_query": "GraphQueryOperations" + }, + "operation_mixins": { + "resource_changes" : { + "sync": { + "signature": "def resource_changes(\n self,\n parameters, # type: \"_models.ResourceChangesRequestParameters\"\n **kwargs # type: Any\n):\n", + "doc": "\"\"\"List changes to a resource for a given time interval.\n\n:param parameters: the parameters for this request for changes.\n:type parameters: ~azure.mgmt.resourcegraph.models.ResourceChangesRequestParameters\n:keyword callable cls: A custom type or function that will be passed the direct response\n:return: ResourceChangeList, or the result of cls(response)\n:rtype: ~azure.mgmt.resourcegraph.models.ResourceChangeList\n:raises: ~azure.core.exceptions.HttpResponseError\n\"\"\"" + }, + "async": { + "coroutine": true, + "signature": "async def resource_changes(\n self,\n parameters: \"_models.ResourceChangesRequestParameters\",\n **kwargs\n) -\u003e \"_models.ResourceChangeList\":\n", + "doc": "\"\"\"List changes to a resource for a given time interval.\n\n:param parameters: the parameters for this request for changes.\n:type parameters: ~azure.mgmt.resourcegraph.models.ResourceChangesRequestParameters\n:keyword callable cls: A custom type or function that will be passed the direct response\n:return: ResourceChangeList, or the result of cls(response)\n:rtype: ~azure.mgmt.resourcegraph.models.ResourceChangeList\n:raises: ~azure.core.exceptions.HttpResponseError\n\"\"\"" + }, + "call": "parameters" + }, + "resource_change_details" : { + "sync": { + "signature": "def resource_change_details(\n self,\n parameters, # type: \"_models.ResourceChangeDetailsRequestParameters\"\n **kwargs # type: Any\n):\n", + "doc": "\"\"\"Get resource change details.\n\n:param parameters: The parameters for this request for resource change details.\n:type parameters: ~azure.mgmt.resourcegraph.models.ResourceChangeDetailsRequestParameters\n:keyword callable cls: A custom type or function that will be passed the direct response\n:return: list of ResourceChangeData, or the result of cls(response)\n:rtype: list[~azure.mgmt.resourcegraph.models.ResourceChangeData]\n:raises: ~azure.core.exceptions.HttpResponseError\n\"\"\"" + }, + "async": { + "coroutine": true, + "signature": "async def resource_change_details(\n self,\n parameters: \"_models.ResourceChangeDetailsRequestParameters\",\n **kwargs\n) -\u003e List[\"_models.ResourceChangeData\"]:\n", + "doc": "\"\"\"Get resource change details.\n\n:param parameters: The parameters for this request for resource change details.\n:type parameters: ~azure.mgmt.resourcegraph.models.ResourceChangeDetailsRequestParameters\n:keyword callable cls: A custom type or function that will be passed the direct response\n:return: list of ResourceChangeData, or the result of cls(response)\n:rtype: list[~azure.mgmt.resourcegraph.models.ResourceChangeData]\n:raises: ~azure.core.exceptions.HttpResponseError\n\"\"\"" + }, + "call": "parameters" + }, + "resources" : { + "sync": { + "signature": "def resources(\n self,\n query, # type: \"_models.QueryRequest\"\n **kwargs # type: Any\n):\n", + "doc": "\"\"\"Queries the resources managed by Azure Resource Manager for scopes specified in the request.\n\n:param query: Request specifying query and its options.\n:type query: ~azure.mgmt.resourcegraph.models.QueryRequest\n:keyword callable cls: A custom type or function that will be passed the direct response\n:return: QueryResponse, or the result of cls(response)\n:rtype: ~azure.mgmt.resourcegraph.models.QueryResponse\n:raises: ~azure.core.exceptions.HttpResponseError\n\"\"\"" + }, + "async": { + "coroutine": true, + "signature": "async def resources(\n self,\n query: \"_models.QueryRequest\",\n **kwargs\n) -\u003e \"_models.QueryResponse\":\n", + "doc": "\"\"\"Queries the resources managed by Azure Resource Manager for scopes specified in the request.\n\n:param query: Request specifying query and its options.\n:type query: ~azure.mgmt.resourcegraph.models.QueryRequest\n:keyword callable cls: A custom type or function that will be passed the direct response\n:return: QueryResponse, or the result of cls(response)\n:rtype: ~azure.mgmt.resourcegraph.models.QueryResponse\n:raises: ~azure.core.exceptions.HttpResponseError\n\"\"\"" + }, + "call": "query" + }, + "resources_history" : { + "sync": { + "signature": "def resources_history(\n self,\n request, # type: \"_models.ResourcesHistoryRequest\"\n **kwargs # type: Any\n):\n", + "doc": "\"\"\"List all snapshots of a resource for a given time interval.\n\n:param request:\n:type request: ~azure.mgmt.resourcegraph.models.ResourcesHistoryRequest\n:keyword callable cls: A custom type or function that will be passed the direct response\n:return: object, or the result of cls(response)\n:rtype: object\n:raises: ~azure.core.exceptions.HttpResponseError\n\"\"\"" + }, + "async": { + "coroutine": true, + "signature": "async def resources_history(\n self,\n request: \"_models.ResourcesHistoryRequest\",\n **kwargs\n) -\u003e object:\n", + "doc": "\"\"\"List all snapshots of a resource for a given time interval.\n\n:param request:\n:type request: ~azure.mgmt.resourcegraph.models.ResourcesHistoryRequest\n:keyword callable cls: A custom type or function that will be passed the direct response\n:return: object, or the result of cls(response)\n:rtype: object\n:raises: ~azure.core.exceptions.HttpResponseError\n\"\"\"" + }, + "call": "request" + } + }, + "sync_imports": "{\"regular\": {\"azurecore\": {\"azure.core.exceptions\": [\"ClientAuthenticationError\", \"HttpResponseError\", \"ResourceExistsError\", \"ResourceNotFoundError\", \"map_error\"], \"azure.mgmt.core.exceptions\": [\"ARMErrorFormat\"], \"azure.core.pipeline\": [\"PipelineResponse\"], \"azure.core.pipeline.transport\": [\"HttpRequest\", \"HttpResponse\"]}, \"stdlib\": {\"warnings\": [null]}}, \"conditional\": {\"stdlib\": {\"typing\": [\"Any\", \"Callable\", \"Dict\", \"Generic\", \"List\", \"Optional\", \"TypeVar\"]}}}", + "async_imports": "{\"regular\": {\"azurecore\": {\"azure.core.exceptions\": [\"ClientAuthenticationError\", \"HttpResponseError\", \"ResourceExistsError\", \"ResourceNotFoundError\", \"map_error\"], \"azure.mgmt.core.exceptions\": [\"ARMErrorFormat\"], \"azure.core.pipeline\": [\"PipelineResponse\"], \"azure.core.pipeline.transport\": [\"AsyncHttpResponse\", \"HttpRequest\"]}, \"stdlib\": {\"warnings\": [null]}}, \"conditional\": {\"stdlib\": {\"typing\": [\"Any\", \"Callable\", \"Dict\", \"Generic\", \"List\", \"Optional\", \"TypeVar\"]}}}" +} \ No newline at end of file diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/_resource_graph_client.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/_resource_graph_client.py new file mode 100644 index 00000000000..cc07fe0aad8 --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/_resource_graph_client.py @@ -0,0 +1,74 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from typing import TYPE_CHECKING + +from azure.mgmt.core import ARMPipelineClient +from msrest import Deserializer, Serializer + +if TYPE_CHECKING: + # pylint: disable=unused-import,ungrouped-imports + from typing import Any, Optional + + from azure.core.credentials import TokenCredential + +from ._configuration import ResourceGraphClientConfiguration +from .operations import ResourceGraphClientOperationsMixin +from .operations import Operations +from .operations import GraphQueryOperations +from . import models + + +class ResourceGraphClient(ResourceGraphClientOperationsMixin): + """Azure Resource Graph API Reference. + + :ivar operations: Operations operations + :vartype operations: azure.mgmt.resourcegraph.operations.Operations + :ivar graph_query: GraphQueryOperations operations + :vartype graph_query: azure.mgmt.resourcegraph.operations.GraphQueryOperations + :param credential: Credential needed for the client to connect to Azure. + :type credential: ~azure.core.credentials.TokenCredential + :param subscription_id: The Azure subscription Id. + :type subscription_id: str + :param str base_url: Service URL + """ + + def __init__( + self, + credential, # type: "TokenCredential" + subscription_id, # type: str + base_url=None, # type: Optional[str] + **kwargs # type: Any + ): + # type: (...) -> None + if not base_url: + base_url = 'https://management.azure.com' + self._config = ResourceGraphClientConfiguration(credential, subscription_id, **kwargs) + self._client = ARMPipelineClient(base_url=base_url, config=self._config, **kwargs) + + client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} + self._serialize = Serializer(client_models) + self._deserialize = Deserializer(client_models) + + self.operations = Operations( + self._client, self._config, self._serialize, self._deserialize) + self.graph_query = GraphQueryOperations( + self._client, self._config, self._serialize, self._deserialize) + + def close(self): + # type: () -> None + self._client.close() + + def __enter__(self): + # type: () -> ResourceGraphClient + self._client.__enter__() + return self + + def __exit__(self, *exc_details): + # type: (Any) -> None + self._client.__exit__(*exc_details) diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/_version.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/_version.py new file mode 100644 index 00000000000..c1257f7f4e1 --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/_version.py @@ -0,0 +1,9 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +VERSION = "10.1.0" diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/__init__.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/__init__.py new file mode 100644 index 00000000000..ec4d8b3a968 --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/__init__.py @@ -0,0 +1,10 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from ._resource_graph_client import ResourceGraphClient +__all__ = ['ResourceGraphClient'] diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/_configuration.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/_configuration.py new file mode 100644 index 00000000000..2d5c6b5e5ae --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/_configuration.py @@ -0,0 +1,66 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from typing import Any, TYPE_CHECKING + +from azure.core.configuration import Configuration +from azure.core.pipeline import policies +from azure.mgmt.core.policies import ARMHttpLoggingPolicy + +from .._version import VERSION + +if TYPE_CHECKING: + # pylint: disable=unused-import,ungrouped-imports + from azure.core.credentials_async import AsyncTokenCredential + + +class ResourceGraphClientConfiguration(Configuration): + """Configuration for ResourceGraphClient. + + Note that all parameters used to create this instance are saved as instance + attributes. + + :param credential: Credential needed for the client to connect to Azure. + :type credential: ~azure.core.credentials_async.AsyncTokenCredential + :param subscription_id: The Azure subscription Id. + :type subscription_id: str + """ + + def __init__( + self, + credential: "AsyncTokenCredential", + subscription_id: str, + **kwargs: Any + ) -> None: + if credential is None: + raise ValueError("Parameter 'credential' must not be None.") + if subscription_id is None: + raise ValueError("Parameter 'subscription_id' must not be None.") + super(ResourceGraphClientConfiguration, self).__init__(**kwargs) + + self.credential = credential + self.subscription_id = subscription_id + self.credential_scopes = kwargs.pop('credential_scopes', ['https://management.azure.com/.default']) + kwargs.setdefault('sdk_moniker', 'mgmt-resourcegraph/{}'.format(VERSION)) + self._configure(**kwargs) + + def _configure( + self, + **kwargs: Any + ) -> None: + self.user_agent_policy = kwargs.get('user_agent_policy') or policies.UserAgentPolicy(**kwargs) + self.headers_policy = kwargs.get('headers_policy') or policies.HeadersPolicy(**kwargs) + self.proxy_policy = kwargs.get('proxy_policy') or policies.ProxyPolicy(**kwargs) + self.logging_policy = kwargs.get('logging_policy') or policies.NetworkTraceLoggingPolicy(**kwargs) + self.http_logging_policy = kwargs.get('http_logging_policy') or ARMHttpLoggingPolicy(**kwargs) + self.retry_policy = kwargs.get('retry_policy') or policies.AsyncRetryPolicy(**kwargs) + self.custom_hook_policy = kwargs.get('custom_hook_policy') or policies.CustomHookPolicy(**kwargs) + self.redirect_policy = kwargs.get('redirect_policy') or policies.AsyncRedirectPolicy(**kwargs) + self.authentication_policy = kwargs.get('authentication_policy') + if self.credential and not self.authentication_policy: + self.authentication_policy = policies.AsyncBearerTokenCredentialPolicy(self.credential, *self.credential_scopes, **kwargs) diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/_resource_graph_client.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/_resource_graph_client.py new file mode 100644 index 00000000000..6e7e1328b17 --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/_resource_graph_client.py @@ -0,0 +1,68 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from typing import Any, Optional, TYPE_CHECKING + +from azure.mgmt.core import AsyncARMPipelineClient +from msrest import Deserializer, Serializer + +if TYPE_CHECKING: + # pylint: disable=unused-import,ungrouped-imports + from azure.core.credentials_async import AsyncTokenCredential + +from ._configuration import ResourceGraphClientConfiguration +from .operations import ResourceGraphClientOperationsMixin +from .operations import Operations +from .operations import GraphQueryOperations +from .. import models + + +class ResourceGraphClient(ResourceGraphClientOperationsMixin): + """Azure Resource Graph API Reference. + + :ivar operations: Operations operations + :vartype operations: azure.mgmt.resourcegraph.aio.operations.Operations + :ivar graph_query: GraphQueryOperations operations + :vartype graph_query: azure.mgmt.resourcegraph.aio.operations.GraphQueryOperations + :param credential: Credential needed for the client to connect to Azure. + :type credential: ~azure.core.credentials_async.AsyncTokenCredential + :param subscription_id: The Azure subscription Id. + :type subscription_id: str + :param str base_url: Service URL + """ + + def __init__( + self, + credential: "AsyncTokenCredential", + subscription_id: str, + base_url: Optional[str] = None, + **kwargs: Any + ) -> None: + if not base_url: + base_url = 'https://management.azure.com' + self._config = ResourceGraphClientConfiguration(credential, subscription_id, **kwargs) + self._client = AsyncARMPipelineClient(base_url=base_url, config=self._config, **kwargs) + + client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} + self._serialize = Serializer(client_models) + self._deserialize = Deserializer(client_models) + + self.operations = Operations( + self._client, self._config, self._serialize, self._deserialize) + self.graph_query = GraphQueryOperations( + self._client, self._config, self._serialize, self._deserialize) + + async def close(self) -> None: + await self._client.close() + + async def __aenter__(self) -> "ResourceGraphClient": + await self._client.__aenter__() + return self + + async def __aexit__(self, *exc_details) -> None: + await self._client.__aexit__(*exc_details) diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/__init__.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/__init__.py new file mode 100644 index 00000000000..21a67ce4dae --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/__init__.py @@ -0,0 +1,17 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from ._resource_graph_client_operations import ResourceGraphClientOperationsMixin +from ._operations import Operations +from ._graph_query_operations import GraphQueryOperations + +__all__ = [ + 'ResourceGraphClientOperationsMixin', + 'Operations', + 'GraphQueryOperations', +] diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/_graph_query_operations.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/_graph_query_operations.py new file mode 100644 index 00000000000..3f937c41b76 --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/_graph_query_operations.py @@ -0,0 +1,362 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +from typing import Any, AsyncIterable, Callable, Dict, Generic, Optional, TypeVar +import warnings + +from azure.core.async_paging import AsyncItemPaged, AsyncList +from azure.core.exceptions import ClientAuthenticationError, HttpResponseError, ResourceExistsError, ResourceNotFoundError, map_error +from azure.core.pipeline import PipelineResponse +from azure.core.pipeline.transport import AsyncHttpResponse, HttpRequest +from azure.mgmt.core.exceptions import ARMErrorFormat + +from ... import models as _models + +T = TypeVar('T') +ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, Dict[str, Any]], Any]] + +class GraphQueryOperations: + """GraphQueryOperations async operations. + + You should not instantiate this class directly. Instead, you should create a Client instance that + instantiates it for you and attaches it as an attribute. + + :ivar models: Alias to model classes used in this operation group. + :type models: ~azure.mgmt.resourcegraph.models + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + """ + + models = _models + + def __init__(self, client, config, serializer, deserializer) -> None: + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self._config = config + + def list( + self, + resource_group_name: str, + **kwargs + ) -> AsyncIterable["_models.GraphQueryListResult"]: + """Get all graph queries defined within a specified subscription and resource group. + + :param resource_group_name: The name of the resource group. + :type resource_group_name: str + :keyword callable cls: A custom type or function that will be passed the direct response + :return: An iterator like instance of either GraphQueryListResult or the result of cls(response) + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.mgmt.resourcegraph.models.GraphQueryListResult] + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.GraphQueryListResult"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2018-09-01-preview" + accept = "application/json" + + def prepare_request(next_link=None): + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + if not next_link: + # Construct URL + url = self.list.metadata['url'] # type: ignore + path_format_arguments = { + 'subscriptionId': self._serialize.url("self._config.subscription_id", self._config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str'), + } + url = self._client.format_url(url, **path_format_arguments) + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + request = self._client.get(url, query_parameters, header_parameters) + else: + url = next_link + query_parameters = {} # type: Dict[str, Any] + request = self._client.get(url, query_parameters, header_parameters) + return request + + async def extract_data(pipeline_response): + deserialized = self._deserialize('GraphQueryListResult', pipeline_response) + list_of_elem = deserialized.value + if cls: + list_of_elem = cls(list_of_elem) + return deserialized.next_link or None, AsyncList(list_of_elem) + + async def get_next(next_link=None): + request = prepare_request(next_link) + + pipeline_response = await self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + error = self._deserialize(_models.GraphQueryError, response) + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + return pipeline_response + + return AsyncItemPaged( + get_next, extract_data + ) + list.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ResourceGraph/queries'} # type: ignore + + async def get( + self, + resource_group_name: str, + resource_name: str, + **kwargs + ) -> "_models.GraphQueryResource": + """Get a single graph query by its resourceName. + + :param resource_group_name: The name of the resource group. + :type resource_group_name: str + :param resource_name: The name of the Graph Query resource. + :type resource_name: str + :keyword callable cls: A custom type or function that will be passed the direct response + :return: GraphQueryResource, or the result of cls(response) + :rtype: ~azure.mgmt.resourcegraph.models.GraphQueryResource + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.GraphQueryResource"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2018-09-01-preview" + accept = "application/json" + + # Construct URL + url = self.get.metadata['url'] # type: ignore + path_format_arguments = { + 'subscriptionId': self._serialize.url("self._config.subscription_id", self._config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str'), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str'), + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + request = self._client.get(url, query_parameters, header_parameters) + pipeline_response = await self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.GraphQueryError, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('GraphQueryResource', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + get.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ResourceGraph/queries/{resourceName}'} # type: ignore + + async def delete( + self, + resource_group_name: str, + resource_name: str, + **kwargs + ) -> None: + """Delete a graph query. + + :param resource_group_name: The name of the resource group. + :type resource_group_name: str + :param resource_name: The name of the Graph Query resource. + :type resource_name: str + :keyword callable cls: A custom type or function that will be passed the direct response + :return: None, or the result of cls(response) + :rtype: None + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType[None] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2018-09-01-preview" + accept = "application/json" + + # Construct URL + url = self.delete.metadata['url'] # type: ignore + path_format_arguments = { + 'subscriptionId': self._serialize.url("self._config.subscription_id", self._config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str'), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str'), + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + request = self._client.delete(url, query_parameters, header_parameters) + pipeline_response = await self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200, 204]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.GraphQueryError, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + if cls: + return cls(pipeline_response, None, {}) + + delete.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ResourceGraph/queries/{resourceName}'} # type: ignore + + async def create_or_update( + self, + resource_group_name: str, + resource_name: str, + properties: "_models.GraphQueryResource", + **kwargs + ) -> "_models.GraphQueryResource": + """Create a new graph query. + + :param resource_group_name: The name of the resource group. + :type resource_group_name: str + :param resource_name: The name of the Graph Query resource. + :type resource_name: str + :param properties: Properties that need to be specified to create a new graph query. + :type properties: ~azure.mgmt.resourcegraph.models.GraphQueryResource + :keyword callable cls: A custom type or function that will be passed the direct response + :return: GraphQueryResource, or the result of cls(response) + :rtype: ~azure.mgmt.resourcegraph.models.GraphQueryResource + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.GraphQueryResource"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2018-09-01-preview" + content_type = kwargs.pop("content_type", "application/json") + accept = "application/json" + + # Construct URL + url = self.create_or_update.metadata['url'] # type: ignore + path_format_arguments = { + 'subscriptionId': self._serialize.url("self._config.subscription_id", self._config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str'), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str'), + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = self._serialize.header("content_type", content_type, 'str') + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + body_content = self._serialize.body(properties, 'GraphQueryResource') + body_content_kwargs['content'] = body_content + request = self._client.put(url, query_parameters, header_parameters, **body_content_kwargs) + pipeline_response = await self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.GraphQueryError, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('GraphQueryResource', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + create_or_update.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ResourceGraph/queries/{resourceName}'} # type: ignore + + async def update( + self, + resource_group_name: str, + resource_name: str, + body: "_models.GraphQueryUpdateParameters", + **kwargs + ) -> "_models.GraphQueryResource": + """Updates a graph query that has already been added. + + :param resource_group_name: The name of the resource group. + :type resource_group_name: str + :param resource_name: The name of the Graph Query resource. + :type resource_name: str + :param body: Properties that need to be specified to create a new graph query. + :type body: ~azure.mgmt.resourcegraph.models.GraphQueryUpdateParameters + :keyword callable cls: A custom type or function that will be passed the direct response + :return: GraphQueryResource, or the result of cls(response) + :rtype: ~azure.mgmt.resourcegraph.models.GraphQueryResource + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.GraphQueryResource"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2018-09-01-preview" + content_type = kwargs.pop("content_type", "application/json") + accept = "application/json" + + # Construct URL + url = self.update.metadata['url'] # type: ignore + path_format_arguments = { + 'subscriptionId': self._serialize.url("self._config.subscription_id", self._config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str'), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str'), + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = self._serialize.header("content_type", content_type, 'str') + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + body_content = self._serialize.body(body, 'GraphQueryUpdateParameters') + body_content_kwargs['content'] = body_content + request = self._client.patch(url, query_parameters, header_parameters, **body_content_kwargs) + pipeline_response = await self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.GraphQueryError, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('GraphQueryResource', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + update.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ResourceGraph/queries/{resourceName}'} # type: ignore diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/_operations.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/_operations.py new file mode 100644 index 00000000000..d824ff3399e --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/_operations.py @@ -0,0 +1,105 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +from typing import Any, AsyncIterable, Callable, Dict, Generic, Optional, TypeVar +import warnings + +from azure.core.async_paging import AsyncItemPaged, AsyncList +from azure.core.exceptions import ClientAuthenticationError, HttpResponseError, ResourceExistsError, ResourceNotFoundError, map_error +from azure.core.pipeline import PipelineResponse +from azure.core.pipeline.transport import AsyncHttpResponse, HttpRequest +from azure.mgmt.core.exceptions import ARMErrorFormat + +from ... import models as _models + +T = TypeVar('T') +ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, Dict[str, Any]], Any]] + +class Operations: + """Operations async operations. + + You should not instantiate this class directly. Instead, you should create a Client instance that + instantiates it for you and attaches it as an attribute. + + :ivar models: Alias to model classes used in this operation group. + :type models: ~azure.mgmt.resourcegraph.models + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + """ + + models = _models + + def __init__(self, client, config, serializer, deserializer) -> None: + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self._config = config + + def list( + self, + **kwargs + ) -> AsyncIterable["_models.OperationListResult"]: + """Lists all of the available REST API operations. + + :keyword callable cls: A custom type or function that will be passed the direct response + :return: An iterator like instance of either OperationListResult or the result of cls(response) + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.mgmt.resourcegraph.models.OperationListResult] + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.OperationListResult"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2021-03-01" + accept = "application/json" + + def prepare_request(next_link=None): + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + if not next_link: + # Construct URL + url = self.list.metadata['url'] # type: ignore + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + request = self._client.get(url, query_parameters, header_parameters) + else: + url = next_link + query_parameters = {} # type: Dict[str, Any] + request = self._client.get(url, query_parameters, header_parameters) + return request + + async def extract_data(pipeline_response): + deserialized = self._deserialize('OperationListResult', pipeline_response) + list_of_elem = deserialized.value + if cls: + list_of_elem = cls(list_of_elem) + return None, AsyncList(list_of_elem) + + async def get_next(next_link=None): + request = prepare_request(next_link) + + pipeline_response = await self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + error = self._deserialize(_models.ErrorResponse, response) + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + return pipeline_response + + return AsyncItemPaged( + get_next, extract_data + ) + list.metadata = {'url': '/providers/Microsoft.ResourceGraph/operations'} # type: ignore diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/_resource_graph_client_operations.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/_resource_graph_client_operations.py new file mode 100644 index 00000000000..ada2db38932 --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/aio/operations/_resource_graph_client_operations.py @@ -0,0 +1,241 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +from typing import Any, Callable, Dict, Generic, List, Optional, TypeVar +import warnings + +from azure.core.exceptions import ClientAuthenticationError, HttpResponseError, ResourceExistsError, ResourceNotFoundError, map_error +from azure.core.pipeline import PipelineResponse +from azure.core.pipeline.transport import AsyncHttpResponse, HttpRequest +from azure.mgmt.core.exceptions import ARMErrorFormat + +from ... import models as _models + +T = TypeVar('T') +ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, Dict[str, Any]], Any]] + +class ResourceGraphClientOperationsMixin: + + async def resource_changes( + self, + parameters: "_models.ResourceChangesRequestParameters", + **kwargs + ) -> "_models.ResourceChangeList": + """List changes to a resource for a given time interval. + + :param parameters: the parameters for this request for changes. + :type parameters: ~azure.mgmt.resourcegraph.models.ResourceChangesRequestParameters + :keyword callable cls: A custom type or function that will be passed the direct response + :return: ResourceChangeList, or the result of cls(response) + :rtype: ~azure.mgmt.resourcegraph.models.ResourceChangeList + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.ResourceChangeList"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2020-09-01-preview" + content_type = kwargs.pop("content_type", "application/json") + accept = "application/json" + + # Construct URL + url = self.resource_changes.metadata['url'] # type: ignore + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = self._serialize.header("content_type", content_type, 'str') + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + body_content = self._serialize.body(parameters, 'ResourceChangesRequestParameters') + body_content_kwargs['content'] = body_content + request = self._client.post(url, query_parameters, header_parameters, **body_content_kwargs) + pipeline_response = await self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.ErrorResponse, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('ResourceChangeList', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + resource_changes.metadata = {'url': '/providers/Microsoft.ResourceGraph/resourceChanges'} # type: ignore + + async def resource_change_details( + self, + parameters: "_models.ResourceChangeDetailsRequestParameters", + **kwargs + ) -> List["_models.ResourceChangeData"]: + """Get resource change details. + + :param parameters: The parameters for this request for resource change details. + :type parameters: ~azure.mgmt.resourcegraph.models.ResourceChangeDetailsRequestParameters + :keyword callable cls: A custom type or function that will be passed the direct response + :return: list of ResourceChangeData, or the result of cls(response) + :rtype: list[~azure.mgmt.resourcegraph.models.ResourceChangeData] + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType[List["_models.ResourceChangeData"]] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2020-09-01-preview" + content_type = kwargs.pop("content_type", "application/json") + accept = "application/json" + + # Construct URL + url = self.resource_change_details.metadata['url'] # type: ignore + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = self._serialize.header("content_type", content_type, 'str') + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + body_content = self._serialize.body(parameters, 'ResourceChangeDetailsRequestParameters') + body_content_kwargs['content'] = body_content + request = self._client.post(url, query_parameters, header_parameters, **body_content_kwargs) + pipeline_response = await self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.ErrorResponse, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('[ResourceChangeData]', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + resource_change_details.metadata = {'url': '/providers/Microsoft.ResourceGraph/resourceChangeDetails'} # type: ignore + + async def resources( + self, + query: "_models.QueryRequest", + **kwargs + ) -> "_models.QueryResponse": + """Queries the resources managed by Azure Resource Manager for scopes specified in the request. + + :param query: Request specifying query and its options. + :type query: ~azure.mgmt.resourcegraph.models.QueryRequest + :keyword callable cls: A custom type or function that will be passed the direct response + :return: QueryResponse, or the result of cls(response) + :rtype: ~azure.mgmt.resourcegraph.models.QueryResponse + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.QueryResponse"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2021-03-01" + content_type = kwargs.pop("content_type", "application/json") + accept = "application/json" + + # Construct URL + url = self.resources.metadata['url'] # type: ignore + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = self._serialize.header("content_type", content_type, 'str') + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + body_content = self._serialize.body(query, 'QueryRequest') + body_content_kwargs['content'] = body_content + request = self._client.post(url, query_parameters, header_parameters, **body_content_kwargs) + pipeline_response = await self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.ErrorResponse, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('QueryResponse', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + resources.metadata = {'url': '/providers/Microsoft.ResourceGraph/resources'} # type: ignore + + async def resources_history( + self, + request: "_models.ResourcesHistoryRequest", + **kwargs + ) -> object: + """List all snapshots of a resource for a given time interval. + + :param request: + :type request: ~azure.mgmt.resourcegraph.models.ResourcesHistoryRequest + :keyword callable cls: A custom type or function that will be passed the direct response + :return: object, or the result of cls(response) + :rtype: object + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType[object] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2020-04-01-preview" + content_type = kwargs.pop("content_type", "application/json") + accept = "application/json" + + # Construct URL + url = self.resources_history.metadata['url'] # type: ignore + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = self._serialize.header("content_type", content_type, 'str') + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + body_content = self._serialize.body(request, 'ResourcesHistoryRequest') + body_content_kwargs['content'] = body_content + request = self._client.post(url, query_parameters, header_parameters, **body_content_kwargs) + pipeline_response = await self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.ErrorResponse, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('object', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + resources_history.metadata = {'url': '/providers/Microsoft.ResourceGraph/resourcesHistory'} # type: ignore diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/models/__init__.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/models/__init__.py new file mode 100644 index 00000000000..fa87792c8fd --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/models/__init__.py @@ -0,0 +1,136 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +try: + from ._models_py3 import Column + from ._models_py3 import DateTimeInterval + from ._models_py3 import Error + from ._models_py3 import ErrorDetails + from ._models_py3 import ErrorFieldContract + from ._models_py3 import ErrorResponse + from ._models_py3 import Facet + from ._models_py3 import FacetError + from ._models_py3 import FacetRequest + from ._models_py3 import FacetRequestOptions + from ._models_py3 import FacetResult + from ._models_py3 import GraphQueryError + from ._models_py3 import GraphQueryListResult + from ._models_py3 import GraphQueryResource + from ._models_py3 import GraphQueryUpdateParameters + from ._models_py3 import Operation + from ._models_py3 import OperationDisplay + from ._models_py3 import OperationListResult + from ._models_py3 import QueryRequest + from ._models_py3 import QueryRequestOptions + from ._models_py3 import QueryResponse + from ._models_py3 import Resource + from ._models_py3 import ResourceChangeData + from ._models_py3 import ResourceChangeDataAfterSnapshot + from ._models_py3 import ResourceChangeDataBeforeSnapshot + from ._models_py3 import ResourceChangeDetailsRequestParameters + from ._models_py3 import ResourceChangeList + from ._models_py3 import ResourceChangesRequestParameters + from ._models_py3 import ResourceChangesRequestParametersInterval + from ._models_py3 import ResourcePropertyChange + from ._models_py3 import ResourceSnapshotData + from ._models_py3 import ResourcesHistoryRequest + from ._models_py3 import ResourcesHistoryRequestOptions + from ._models_py3 import Table +except (SyntaxError, ImportError): + from ._models import Column # type: ignore + from ._models import DateTimeInterval # type: ignore + from ._models import Error # type: ignore + from ._models import ErrorDetails # type: ignore + from ._models import ErrorFieldContract # type: ignore + from ._models import ErrorResponse # type: ignore + from ._models import Facet # type: ignore + from ._models import FacetError # type: ignore + from ._models import FacetRequest # type: ignore + from ._models import FacetRequestOptions # type: ignore + from ._models import FacetResult # type: ignore + from ._models import GraphQueryError # type: ignore + from ._models import GraphQueryListResult # type: ignore + from ._models import GraphQueryResource # type: ignore + from ._models import GraphQueryUpdateParameters # type: ignore + from ._models import Operation # type: ignore + from ._models import OperationDisplay # type: ignore + from ._models import OperationListResult # type: ignore + from ._models import QueryRequest # type: ignore + from ._models import QueryRequestOptions # type: ignore + from ._models import QueryResponse # type: ignore + from ._models import Resource # type: ignore + from ._models import ResourceChangeData # type: ignore + from ._models import ResourceChangeDataAfterSnapshot # type: ignore + from ._models import ResourceChangeDataBeforeSnapshot # type: ignore + from ._models import ResourceChangeDetailsRequestParameters # type: ignore + from ._models import ResourceChangeList # type: ignore + from ._models import ResourceChangesRequestParameters # type: ignore + from ._models import ResourceChangesRequestParametersInterval # type: ignore + from ._models import ResourcePropertyChange # type: ignore + from ._models import ResourceSnapshotData # type: ignore + from ._models import ResourcesHistoryRequest # type: ignore + from ._models import ResourcesHistoryRequestOptions # type: ignore + from ._models import Table # type: ignore + +from ._resource_graph_client_enums import ( + ChangeCategory, + ChangeType, + ColumnDataType, + FacetSortOrder, + PropertyChangeType, + ResourcesHistoryRequestOptionsResultFormat, + ResultFormat, + ResultKind, + ResultTruncated, +) + +__all__ = [ + 'Column', + 'DateTimeInterval', + 'Error', + 'ErrorDetails', + 'ErrorFieldContract', + 'ErrorResponse', + 'Facet', + 'FacetError', + 'FacetRequest', + 'FacetRequestOptions', + 'FacetResult', + 'GraphQueryError', + 'GraphQueryListResult', + 'GraphQueryResource', + 'GraphQueryUpdateParameters', + 'Operation', + 'OperationDisplay', + 'OperationListResult', + 'QueryRequest', + 'QueryRequestOptions', + 'QueryResponse', + 'Resource', + 'ResourceChangeData', + 'ResourceChangeDataAfterSnapshot', + 'ResourceChangeDataBeforeSnapshot', + 'ResourceChangeDetailsRequestParameters', + 'ResourceChangeList', + 'ResourceChangesRequestParameters', + 'ResourceChangesRequestParametersInterval', + 'ResourcePropertyChange', + 'ResourceSnapshotData', + 'ResourcesHistoryRequest', + 'ResourcesHistoryRequestOptions', + 'Table', + 'ChangeCategory', + 'ChangeType', + 'ColumnDataType', + 'FacetSortOrder', + 'PropertyChangeType', + 'ResourcesHistoryRequestOptionsResultFormat', + 'ResultFormat', + 'ResultKind', + 'ResultTruncated', +] diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/models/_models.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/models/_models.py new file mode 100644 index 00000000000..3e0f8b2f1c2 --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/models/_models.py @@ -0,0 +1,1239 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from azure.core.exceptions import HttpResponseError +import msrest.serialization + + +class Column(msrest.serialization.Model): + """Query result column descriptor. + + All required parameters must be populated in order to send to Azure. + + :param name: Required. Column name. + :type name: str + :param type: Required. Column data type. Possible values include: "string", "integer", + "number", "boolean", "object". + :type type: str or ~azure.mgmt.resourcegraph.models.ColumnDataType + """ + + _validation = { + 'name': {'required': True}, + 'type': {'required': True}, + } + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__( + self, + **kwargs + ): + super(Column, self).__init__(**kwargs) + self.name = kwargs['name'] + self.type = kwargs['type'] + + +class DateTimeInterval(msrest.serialization.Model): + """An interval in time specifying the date and time for the inclusive start and exclusive end, i.e. ``[start, end)``. + + All required parameters must be populated in order to send to Azure. + + :param start: Required. A datetime indicating the inclusive/closed start of the time interval, + i.e. ``[``\ **\ ``start``\ **\ ``, end)``. Specifying a ``start`` that occurs chronologically + after ``end`` will result in an error. + :type start: ~datetime.datetime + :param end: Required. A datetime indicating the exclusive/open end of the time interval, i.e. + ``[start,``\ **\ ``end``\ **\ ``)``. Specifying an ``end`` that occurs chronologically before + ``start`` will result in an error. + :type end: ~datetime.datetime + """ + + _validation = { + 'start': {'required': True}, + 'end': {'required': True}, + } + + _attribute_map = { + 'start': {'key': 'start', 'type': 'iso-8601'}, + 'end': {'key': 'end', 'type': 'iso-8601'}, + } + + def __init__( + self, + **kwargs + ): + super(DateTimeInterval, self).__init__(**kwargs) + self.start = kwargs['start'] + self.end = kwargs['end'] + + +class Error(msrest.serialization.Model): + """Error details. + + All required parameters must be populated in order to send to Azure. + + :param code: Required. Error code identifying the specific error. + :type code: str + :param message: Required. A human readable error message. + :type message: str + :param details: Error details. + :type details: list[~azure.mgmt.resourcegraph.models.ErrorDetails] + """ + + _validation = { + 'code': {'required': True}, + 'message': {'required': True}, + } + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'details': {'key': 'details', 'type': '[ErrorDetails]'}, + } + + def __init__( + self, + **kwargs + ): + super(Error, self).__init__(**kwargs) + self.code = kwargs['code'] + self.message = kwargs['message'] + self.details = kwargs.get('details', None) + + +class ErrorDetails(msrest.serialization.Model): + """Error details. + + All required parameters must be populated in order to send to Azure. + + :param additional_properties: Unmatched properties from the message are deserialized to this + collection. + :type additional_properties: dict[str, object] + :param code: Required. Error code identifying the specific error. + :type code: str + :param message: Required. A human readable error message. + :type message: str + """ + + _validation = { + 'code': {'required': True}, + 'message': {'required': True}, + } + + _attribute_map = { + 'additional_properties': {'key': '', 'type': '{object}'}, + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + } + + def __init__( + self, + **kwargs + ): + super(ErrorDetails, self).__init__(**kwargs) + self.additional_properties = kwargs.get('additional_properties', None) + self.code = kwargs['code'] + self.message = kwargs['message'] + + +class ErrorFieldContract(msrest.serialization.Model): + """Error Field contract. + + :param code: Property level error code. + :type code: str + :param message: Human-readable representation of property-level error. + :type message: str + :param target: Property name. + :type target: str + """ + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'target': {'key': 'target', 'type': 'str'}, + } + + def __init__( + self, + **kwargs + ): + super(ErrorFieldContract, self).__init__(**kwargs) + self.code = kwargs.get('code', None) + self.message = kwargs.get('message', None) + self.target = kwargs.get('target', None) + + +class ErrorResponse(msrest.serialization.Model): + """An error response from the API. + + All required parameters must be populated in order to send to Azure. + + :param error: Required. Error information. + :type error: ~azure.mgmt.resourcegraph.models.Error + """ + + _validation = { + 'error': {'required': True}, + } + + _attribute_map = { + 'error': {'key': 'error', 'type': 'Error'}, + } + + def __init__( + self, + **kwargs + ): + super(ErrorResponse, self).__init__(**kwargs) + self.error = kwargs['error'] + + +class Facet(msrest.serialization.Model): + """A facet containing additional statistics on the response of a query. Can be either FacetResult or FacetError. + + You probably want to use the sub-classes and not this class directly. Known + sub-classes are: FacetError, FacetResult. + + All required parameters must be populated in order to send to Azure. + + :param expression: Required. Facet expression, same as in the corresponding facet request. + :type expression: str + :param result_type: Required. Result type.Constant filled by server. + :type result_type: str + """ + + _validation = { + 'expression': {'required': True}, + 'result_type': {'required': True}, + } + + _attribute_map = { + 'expression': {'key': 'expression', 'type': 'str'}, + 'result_type': {'key': 'resultType', 'type': 'str'}, + } + + _subtype_map = { + 'result_type': {'FacetError': 'FacetError', 'FacetResult': 'FacetResult'} + } + + def __init__( + self, + **kwargs + ): + super(Facet, self).__init__(**kwargs) + self.expression = kwargs['expression'] + self.result_type = None # type: Optional[str] + + +class FacetError(Facet): + """A facet whose execution resulted in an error. + + All required parameters must be populated in order to send to Azure. + + :param expression: Required. Facet expression, same as in the corresponding facet request. + :type expression: str + :param result_type: Required. Result type.Constant filled by server. + :type result_type: str + :param errors: Required. An array containing detected facet errors with details. + :type errors: list[~azure.mgmt.resourcegraph.models.ErrorDetails] + """ + + _validation = { + 'expression': {'required': True}, + 'result_type': {'required': True}, + 'errors': {'required': True}, + } + + _attribute_map = { + 'expression': {'key': 'expression', 'type': 'str'}, + 'result_type': {'key': 'resultType', 'type': 'str'}, + 'errors': {'key': 'errors', 'type': '[ErrorDetails]'}, + } + + def __init__( + self, + **kwargs + ): + super(FacetError, self).__init__(**kwargs) + self.result_type = 'FacetError' # type: str + self.errors = kwargs['errors'] + + +class FacetRequest(msrest.serialization.Model): + """A request to compute additional statistics (facets) over the query results. + + All required parameters must be populated in order to send to Azure. + + :param expression: Required. The column or list of columns to summarize by. + :type expression: str + :param options: The options for facet evaluation. + :type options: ~azure.mgmt.resourcegraph.models.FacetRequestOptions + """ + + _validation = { + 'expression': {'required': True}, + } + + _attribute_map = { + 'expression': {'key': 'expression', 'type': 'str'}, + 'options': {'key': 'options', 'type': 'FacetRequestOptions'}, + } + + def __init__( + self, + **kwargs + ): + super(FacetRequest, self).__init__(**kwargs) + self.expression = kwargs['expression'] + self.options = kwargs.get('options', None) + + +class FacetRequestOptions(msrest.serialization.Model): + """The options for facet evaluation. + + :param sort_by: The column name or query expression to sort on. Defaults to count if not + present. + :type sort_by: str + :param sort_order: The sorting order by the selected column (count by default). Possible values + include: "asc", "desc". Default value: "desc". + :type sort_order: str or ~azure.mgmt.resourcegraph.models.FacetSortOrder + :param filter: Specifies the filter condition for the 'where' clause which will be run on main + query's result, just before the actual faceting. + :type filter: str + :param top: The maximum number of facet rows that should be returned. + :type top: int + """ + + _validation = { + 'top': {'maximum': 1000, 'minimum': 1}, + } + + _attribute_map = { + 'sort_by': {'key': 'sortBy', 'type': 'str'}, + 'sort_order': {'key': 'sortOrder', 'type': 'str'}, + 'filter': {'key': 'filter', 'type': 'str'}, + 'top': {'key': '$top', 'type': 'int'}, + } + + def __init__( + self, + **kwargs + ): + super(FacetRequestOptions, self).__init__(**kwargs) + self.sort_by = kwargs.get('sort_by', None) + self.sort_order = kwargs.get('sort_order', "desc") + self.filter = kwargs.get('filter', None) + self.top = kwargs.get('top', None) + + +class FacetResult(Facet): + """Successfully executed facet containing additional statistics on the response of a query. + + All required parameters must be populated in order to send to Azure. + + :param expression: Required. Facet expression, same as in the corresponding facet request. + :type expression: str + :param result_type: Required. Result type.Constant filled by server. + :type result_type: str + :param total_records: Required. Number of total records in the facet results. + :type total_records: long + :param count: Required. Number of records returned in the facet response. + :type count: int + :param data: Required. A JObject array or Table containing the desired facets. Only present if + the facet is valid. + :type data: object + """ + + _validation = { + 'expression': {'required': True}, + 'result_type': {'required': True}, + 'total_records': {'required': True}, + 'count': {'required': True}, + 'data': {'required': True}, + } + + _attribute_map = { + 'expression': {'key': 'expression', 'type': 'str'}, + 'result_type': {'key': 'resultType', 'type': 'str'}, + 'total_records': {'key': 'totalRecords', 'type': 'long'}, + 'count': {'key': 'count', 'type': 'int'}, + 'data': {'key': 'data', 'type': 'object'}, + } + + def __init__( + self, + **kwargs + ): + super(FacetResult, self).__init__(**kwargs) + self.result_type = 'FacetResult' # type: str + self.total_records = kwargs['total_records'] + self.count = kwargs['count'] + self.data = kwargs['data'] + + +class GraphQueryError(msrest.serialization.Model): + """Error message body that will indicate why the operation failed. + + :param code: Service-defined error code. This code serves as a sub-status for the HTTP error + code specified in the response. + :type code: str + :param message: Human-readable representation of the error. + :type message: str + :param details: The list of invalid fields send in request, in case of validation error. + :type details: list[~azure.mgmt.resourcegraph.models.ErrorFieldContract] + """ + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'details': {'key': 'details', 'type': '[ErrorFieldContract]'}, + } + + def __init__( + self, + **kwargs + ): + super(GraphQueryError, self).__init__(**kwargs) + self.code = kwargs.get('code', None) + self.message = kwargs.get('message', None) + self.details = kwargs.get('details', None) + + +class GraphQueryListResult(msrest.serialization.Model): + """Graph query list result. + + Variables are only populated by the server, and will be ignored when sending a request. + + :param next_link: URL to fetch the next set of queries. + :type next_link: str + :ivar value: An array of graph queries. + :vartype value: list[~azure.mgmt.resourcegraph.models.GraphQueryResource] + """ + + _validation = { + 'value': {'readonly': True}, + } + + _attribute_map = { + 'next_link': {'key': 'nextLink', 'type': 'str'}, + 'value': {'key': 'value', 'type': '[GraphQueryResource]'}, + } + + def __init__( + self, + **kwargs + ): + super(GraphQueryListResult, self).__init__(**kwargs) + self.next_link = kwargs.get('next_link', None) + self.value = None + + +class Resource(msrest.serialization.Model): + """An azure resource object. + + Variables are only populated by the server, and will be ignored when sending a request. + + :ivar id: Azure resource Id. + :vartype id: str + :ivar name: Azure resource name. This is GUID value. The display name should be assigned within + properties field. + :vartype name: str + :param location: The location of the resource. + :type location: str + :ivar type: Azure resource type. + :vartype type: str + :param etag: This will be used to handle Optimistic Concurrency. If not present, it will always + overwrite the existing resource without checking conflict. + :type etag: str + :param tags: A set of tags. Resource tags. + :type tags: dict[str, str] + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True}, + 'type': {'readonly': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'location': {'key': 'location', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + 'etag': {'key': 'etag', 'type': 'str'}, + 'tags': {'key': 'tags', 'type': '{str}'}, + } + + def __init__( + self, + **kwargs + ): + super(Resource, self).__init__(**kwargs) + self.id = None + self.name = None + self.location = kwargs.get('location', None) + self.type = None + self.etag = kwargs.get('etag', None) + self.tags = kwargs.get('tags', None) + + +class GraphQueryResource(Resource): + """Graph Query entity definition. + + Variables are only populated by the server, and will be ignored when sending a request. + + :ivar id: Azure resource Id. + :vartype id: str + :ivar name: Azure resource name. This is GUID value. The display name should be assigned within + properties field. + :vartype name: str + :param location: The location of the resource. + :type location: str + :ivar type: Azure resource type. + :vartype type: str + :param etag: This will be used to handle Optimistic Concurrency. If not present, it will always + overwrite the existing resource without checking conflict. + :type etag: str + :param tags: A set of tags. Resource tags. + :type tags: dict[str, str] + :ivar time_modified: Date and time in UTC of the last modification that was made to this graph + query definition. + :vartype time_modified: ~datetime.datetime + :param description: The description of a graph query. + :type description: str + :param query: KQL query that will be graph. + :type query: str + :ivar result_kind: Enum indicating a type of graph query. Possible values include: "basic". + :vartype result_kind: str or ~azure.mgmt.resourcegraph.models.ResultKind + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True}, + 'type': {'readonly': True}, + 'time_modified': {'readonly': True}, + 'result_kind': {'readonly': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'location': {'key': 'location', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + 'etag': {'key': 'etag', 'type': 'str'}, + 'tags': {'key': 'tags', 'type': '{str}'}, + 'time_modified': {'key': 'properties.timeModified', 'type': 'iso-8601'}, + 'description': {'key': 'properties.description', 'type': 'str'}, + 'query': {'key': 'properties.query', 'type': 'str'}, + 'result_kind': {'key': 'properties.resultKind', 'type': 'str'}, + } + + def __init__( + self, + **kwargs + ): + super(GraphQueryResource, self).__init__(**kwargs) + self.time_modified = None + self.description = kwargs.get('description', None) + self.query = kwargs.get('query', None) + self.result_kind = None + + +class GraphQueryUpdateParameters(msrest.serialization.Model): + """The parameters that can be provided when updating workbook properties properties. + + :param tags: A set of tags. Resource tags. + :type tags: dict[str, str] + :param etag: This will be used to handle Optimistic Concurrency. If not present, it will always + overwrite the existing resource without checking conflict. + :type etag: str + :param description: The description of a graph query. + :type description: str + :param query: KQL query that will be graph. + :type query: str + """ + + _attribute_map = { + 'tags': {'key': 'tags', 'type': '{str}'}, + 'etag': {'key': 'etag', 'type': 'str'}, + 'description': {'key': 'properties.description', 'type': 'str'}, + 'query': {'key': 'properties.query', 'type': 'str'}, + } + + def __init__( + self, + **kwargs + ): + super(GraphQueryUpdateParameters, self).__init__(**kwargs) + self.tags = kwargs.get('tags', None) + self.etag = kwargs.get('etag', None) + self.description = kwargs.get('description', None) + self.query = kwargs.get('query', None) + + +class Operation(msrest.serialization.Model): + """Resource Graph REST API operation definition. + + :param name: Operation name: {provider}/{resource}/{operation}. + :type name: str + :param display: Display metadata associated with the operation. + :type display: ~azure.mgmt.resourcegraph.models.OperationDisplay + :param origin: The origin of operations. + :type origin: str + """ + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'display': {'key': 'display', 'type': 'OperationDisplay'}, + 'origin': {'key': 'origin', 'type': 'str'}, + } + + def __init__( + self, + **kwargs + ): + super(Operation, self).__init__(**kwargs) + self.name = kwargs.get('name', None) + self.display = kwargs.get('display', None) + self.origin = kwargs.get('origin', None) + + +class OperationDisplay(msrest.serialization.Model): + """Display metadata associated with the operation. + + :param provider: Service provider: Microsoft Resource Graph. + :type provider: str + :param resource: Resource on which the operation is performed etc. + :type resource: str + :param operation: Type of operation: get, read, delete, etc. + :type operation: str + :param description: Description for the operation. + :type description: str + """ + + _attribute_map = { + 'provider': {'key': 'provider', 'type': 'str'}, + 'resource': {'key': 'resource', 'type': 'str'}, + 'operation': {'key': 'operation', 'type': 'str'}, + 'description': {'key': 'description', 'type': 'str'}, + } + + def __init__( + self, + **kwargs + ): + super(OperationDisplay, self).__init__(**kwargs) + self.provider = kwargs.get('provider', None) + self.resource = kwargs.get('resource', None) + self.operation = kwargs.get('operation', None) + self.description = kwargs.get('description', None) + + +class OperationListResult(msrest.serialization.Model): + """Result of the request to list Resource Graph operations. It contains a list of operations and a URL link to get the next set of results. + + :param value: List of Resource Graph operations supported by the Resource Graph resource + provider. + :type value: list[~azure.mgmt.resourcegraph.models.Operation] + """ + + _attribute_map = { + 'value': {'key': 'value', 'type': '[Operation]'}, + } + + def __init__( + self, + **kwargs + ): + super(OperationListResult, self).__init__(**kwargs) + self.value = kwargs.get('value', None) + + +class QueryRequest(msrest.serialization.Model): + """Describes a query to be executed. + + All required parameters must be populated in order to send to Azure. + + :param subscriptions: Azure subscriptions against which to execute the query. + :type subscriptions: list[str] + :param management_groups: Azure management groups against which to execute the query. Example: + [ 'mg1', 'mg2' ]. + :type management_groups: list[str] + :param query: Required. The resources query. + :type query: str + :param options: The query evaluation options. + :type options: ~azure.mgmt.resourcegraph.models.QueryRequestOptions + :param facets: An array of facet requests to be computed against the query result. + :type facets: list[~azure.mgmt.resourcegraph.models.FacetRequest] + """ + + _validation = { + 'query': {'required': True}, + } + + _attribute_map = { + 'subscriptions': {'key': 'subscriptions', 'type': '[str]'}, + 'management_groups': {'key': 'managementGroups', 'type': '[str]'}, + 'query': {'key': 'query', 'type': 'str'}, + 'options': {'key': 'options', 'type': 'QueryRequestOptions'}, + 'facets': {'key': 'facets', 'type': '[FacetRequest]'}, + } + + def __init__( + self, + **kwargs + ): + super(QueryRequest, self).__init__(**kwargs) + self.subscriptions = kwargs.get('subscriptions', None) + self.management_groups = kwargs.get('management_groups', None) + self.query = kwargs['query'] + self.options = kwargs.get('options', None) + self.facets = kwargs.get('facets', None) + + +class QueryRequestOptions(msrest.serialization.Model): + """The options for query evaluation. + + :param skip_token: Continuation token for pagination, capturing the next page size and offset, + as well as the context of the query. + :type skip_token: str + :param top: The maximum number of rows that the query should return. Overrides the page size + when ``$skipToken`` property is present. + :type top: int + :param skip: The number of rows to skip from the beginning of the results. Overrides the next + page offset when ``$skipToken`` property is present. + :type skip: int + :param result_format: Defines in which format query result returned. Possible values include: + "table", "objectArray". Default value: "objectArray". + :type result_format: str or ~azure.mgmt.resourcegraph.models.ResultFormat + :param allow_partial_scopes: Only applicable for tenant and management group level queries to + decide whether to allow partial scopes for result in case the number of subscriptions exceed + allowed limits. + :type allow_partial_scopes: bool + """ + + _validation = { + 'top': {'maximum': 1000, 'minimum': 1}, + 'skip': {'minimum': 0}, + } + + _attribute_map = { + 'skip_token': {'key': '$skipToken', 'type': 'str'}, + 'top': {'key': '$top', 'type': 'int'}, + 'skip': {'key': '$skip', 'type': 'int'}, + 'result_format': {'key': 'resultFormat', 'type': 'str'}, + 'allow_partial_scopes': {'key': 'allowPartialScopes', 'type': 'bool'}, + } + + def __init__( + self, + **kwargs + ): + super(QueryRequestOptions, self).__init__(**kwargs) + self.skip_token = kwargs.get('skip_token', None) + self.top = kwargs.get('top', None) + self.skip = kwargs.get('skip', None) + self.result_format = kwargs.get('result_format', "objectArray") + self.allow_partial_scopes = kwargs.get('allow_partial_scopes', False) + + +class QueryResponse(msrest.serialization.Model): + """Query result. + + All required parameters must be populated in order to send to Azure. + + :param total_records: Required. Number of total records matching the query. + :type total_records: long + :param count: Required. Number of records returned in the current response. In the case of + paging, this is the number of records in the current page. + :type count: long + :param result_truncated: Required. Indicates whether the query results are truncated. Possible + values include: "true", "false". + :type result_truncated: str or ~azure.mgmt.resourcegraph.models.ResultTruncated + :param skip_token: When present, the value can be passed to a subsequent query call (together + with the same query and scopes used in the current request) to retrieve the next page of data. + :type skip_token: str + :param data: Required. Query output in JObject array or Table format. + :type data: object + :param facets: Query facets. + :type facets: list[~azure.mgmt.resourcegraph.models.Facet] + """ + + _validation = { + 'total_records': {'required': True}, + 'count': {'required': True}, + 'result_truncated': {'required': True}, + 'data': {'required': True}, + } + + _attribute_map = { + 'total_records': {'key': 'totalRecords', 'type': 'long'}, + 'count': {'key': 'count', 'type': 'long'}, + 'result_truncated': {'key': 'resultTruncated', 'type': 'str'}, + 'skip_token': {'key': '$skipToken', 'type': 'str'}, + 'data': {'key': 'data', 'type': 'object'}, + 'facets': {'key': 'facets', 'type': '[Facet]'}, + } + + def __init__( + self, + **kwargs + ): + super(QueryResponse, self).__init__(**kwargs) + self.total_records = kwargs['total_records'] + self.count = kwargs['count'] + self.result_truncated = kwargs['result_truncated'] + self.skip_token = kwargs.get('skip_token', None) + self.data = kwargs['data'] + self.facets = kwargs.get('facets', None) + + +class ResourceChangeData(msrest.serialization.Model): + """Data on a specific change, represented by a pair of before and after resource snapshots. + + All required parameters must be populated in order to send to Azure. + + :param resource_id: The resource for a change. + :type resource_id: str + :param change_id: Required. The change ID. Valid and unique within the specified resource only. + :type change_id: str + :param before_snapshot: Required. The snapshot before the change. + :type before_snapshot: ~azure.mgmt.resourcegraph.models.ResourceSnapshotData + :param after_snapshot: Required. The snapshot after the change. + :type after_snapshot: ~azure.mgmt.resourcegraph.models.ResourceSnapshotData + :param change_type: The change type for snapshot. PropertyChanges will be provided in case of + Update change type. Possible values include: "Create", "Update", "Delete". + :type change_type: str or ~azure.mgmt.resourcegraph.models.ChangeType + :param property_changes: An array of resource property change. + :type property_changes: list[~azure.mgmt.resourcegraph.models.ResourcePropertyChange] + """ + + _validation = { + 'change_id': {'required': True}, + 'before_snapshot': {'required': True}, + 'after_snapshot': {'required': True}, + } + + _attribute_map = { + 'resource_id': {'key': 'resourceId', 'type': 'str'}, + 'change_id': {'key': 'changeId', 'type': 'str'}, + 'before_snapshot': {'key': 'beforeSnapshot', 'type': 'ResourceSnapshotData'}, + 'after_snapshot': {'key': 'afterSnapshot', 'type': 'ResourceSnapshotData'}, + 'change_type': {'key': 'changeType', 'type': 'str'}, + 'property_changes': {'key': 'propertyChanges', 'type': '[ResourcePropertyChange]'}, + } + + def __init__( + self, + **kwargs + ): + super(ResourceChangeData, self).__init__(**kwargs) + self.resource_id = kwargs.get('resource_id', None) + self.change_id = kwargs['change_id'] + self.before_snapshot = kwargs['before_snapshot'] + self.after_snapshot = kwargs['after_snapshot'] + self.change_type = kwargs.get('change_type', None) + self.property_changes = kwargs.get('property_changes', None) + + +class ResourceSnapshotData(msrest.serialization.Model): + """Data on a specific resource snapshot. + + All required parameters must be populated in order to send to Azure. + + :param snapshot_id: The ID of the snapshot. + :type snapshot_id: str + :param timestamp: Required. The time when the snapshot was created. + The snapshot timestamp provides an approximation as to when a modification to a resource was + detected. There can be a difference between the actual modification time and the detection + time. This is due to differences in how operations that modify a resource are processed, + versus how operation that record resource snapshots are processed. + :type timestamp: ~datetime.datetime + :param content: The resource snapshot content (in resourceChangeDetails response only). + :type content: object + """ + + _validation = { + 'timestamp': {'required': True}, + } + + _attribute_map = { + 'snapshot_id': {'key': 'snapshotId', 'type': 'str'}, + 'timestamp': {'key': 'timestamp', 'type': 'iso-8601'}, + 'content': {'key': 'content', 'type': 'object'}, + } + + def __init__( + self, + **kwargs + ): + super(ResourceSnapshotData, self).__init__(**kwargs) + self.snapshot_id = kwargs.get('snapshot_id', None) + self.timestamp = kwargs['timestamp'] + self.content = kwargs.get('content', None) + + +class ResourceChangeDataAfterSnapshot(ResourceSnapshotData): + """The snapshot after the change. + + All required parameters must be populated in order to send to Azure. + + :param snapshot_id: The ID of the snapshot. + :type snapshot_id: str + :param timestamp: Required. The time when the snapshot was created. + The snapshot timestamp provides an approximation as to when a modification to a resource was + detected. There can be a difference between the actual modification time and the detection + time. This is due to differences in how operations that modify a resource are processed, + versus how operation that record resource snapshots are processed. + :type timestamp: ~datetime.datetime + :param content: The resource snapshot content (in resourceChangeDetails response only). + :type content: object + """ + + _validation = { + 'timestamp': {'required': True}, + } + + _attribute_map = { + 'snapshot_id': {'key': 'snapshotId', 'type': 'str'}, + 'timestamp': {'key': 'timestamp', 'type': 'iso-8601'}, + 'content': {'key': 'content', 'type': 'object'}, + } + + def __init__( + self, + **kwargs + ): + super(ResourceChangeDataAfterSnapshot, self).__init__(**kwargs) + + +class ResourceChangeDataBeforeSnapshot(ResourceSnapshotData): + """The snapshot before the change. + + All required parameters must be populated in order to send to Azure. + + :param snapshot_id: The ID of the snapshot. + :type snapshot_id: str + :param timestamp: Required. The time when the snapshot was created. + The snapshot timestamp provides an approximation as to when a modification to a resource was + detected. There can be a difference between the actual modification time and the detection + time. This is due to differences in how operations that modify a resource are processed, + versus how operation that record resource snapshots are processed. + :type timestamp: ~datetime.datetime + :param content: The resource snapshot content (in resourceChangeDetails response only). + :type content: object + """ + + _validation = { + 'timestamp': {'required': True}, + } + + _attribute_map = { + 'snapshot_id': {'key': 'snapshotId', 'type': 'str'}, + 'timestamp': {'key': 'timestamp', 'type': 'iso-8601'}, + 'content': {'key': 'content', 'type': 'object'}, + } + + def __init__( + self, + **kwargs + ): + super(ResourceChangeDataBeforeSnapshot, self).__init__(**kwargs) + + +class ResourceChangeDetailsRequestParameters(msrest.serialization.Model): + """The parameters for a specific change details request. + + All required parameters must be populated in order to send to Azure. + + :param resource_ids: Required. Specifies the list of resources for a change details request. + :type resource_ids: list[str] + :param change_ids: Required. Specifies the list of change IDs for a change details request. + :type change_ids: list[str] + """ + + _validation = { + 'resource_ids': {'required': True}, + 'change_ids': {'required': True}, + } + + _attribute_map = { + 'resource_ids': {'key': 'resourceIds', 'type': '[str]'}, + 'change_ids': {'key': 'changeIds', 'type': '[str]'}, + } + + def __init__( + self, + **kwargs + ): + super(ResourceChangeDetailsRequestParameters, self).__init__(**kwargs) + self.resource_ids = kwargs['resource_ids'] + self.change_ids = kwargs['change_ids'] + + +class ResourceChangeList(msrest.serialization.Model): + """A list of changes associated with a resource over a specific time interval. + + :param changes: The pageable value returned by the operation, i.e. a list of changes to the + resource. + + + * The list is ordered from the most recent changes to the least recent changes. + * This list will be empty if there were no changes during the requested interval. + * The ``Before`` snapshot timestamp value of the oldest change can be outside of the specified + time interval. + :type changes: list[~azure.mgmt.resourcegraph.models.ResourceChangeData] + :param skip_token: Skip token that encodes the skip information while executing the current + request. + :type skip_token: object + """ + + _attribute_map = { + 'changes': {'key': 'changes', 'type': '[ResourceChangeData]'}, + 'skip_token': {'key': '$skipToken', 'type': 'object'}, + } + + def __init__( + self, + **kwargs + ): + super(ResourceChangeList, self).__init__(**kwargs) + self.changes = kwargs.get('changes', None) + self.skip_token = kwargs.get('skip_token', None) + + +class ResourceChangesRequestParameters(msrest.serialization.Model): + """The parameters for a specific changes request. + + All required parameters must be populated in order to send to Azure. + + :param resource_ids: Specifies the list of resources for a changes request. + :type resource_ids: list[str] + :param subscription_id: The subscription id of resources to query the changes from. + :type subscription_id: str + :param interval: Required. Specifies the date and time interval for a changes request. + :type interval: ~azure.mgmt.resourcegraph.models.DateTimeInterval + :param skip_token: Acts as the continuation token for paged responses. + :type skip_token: str + :param top: The maximum number of changes the client can accept in a paged response. + :type top: int + :param table: The table name to query resources from. + :type table: str + :param fetch_property_changes: The flag if set to true will fetch property changes. + :type fetch_property_changes: bool + :param fetch_snapshots: The flag if set to true will fetch change snapshots. + :type fetch_snapshots: bool + """ + + _validation = { + 'interval': {'required': True}, + 'top': {'maximum': 1000, 'minimum': 1}, + } + + _attribute_map = { + 'resource_ids': {'key': 'resourceIds', 'type': '[str]'}, + 'subscription_id': {'key': 'subscriptionId', 'type': 'str'}, + 'interval': {'key': 'interval', 'type': 'DateTimeInterval'}, + 'skip_token': {'key': '$skipToken', 'type': 'str'}, + 'top': {'key': '$top', 'type': 'int'}, + 'table': {'key': 'table', 'type': 'str'}, + 'fetch_property_changes': {'key': 'fetchPropertyChanges', 'type': 'bool'}, + 'fetch_snapshots': {'key': 'fetchSnapshots', 'type': 'bool'}, + } + + def __init__( + self, + **kwargs + ): + super(ResourceChangesRequestParameters, self).__init__(**kwargs) + self.resource_ids = kwargs.get('resource_ids', None) + self.subscription_id = kwargs.get('subscription_id', None) + self.interval = kwargs['interval'] + self.skip_token = kwargs.get('skip_token', None) + self.top = kwargs.get('top', None) + self.table = kwargs.get('table', None) + self.fetch_property_changes = kwargs.get('fetch_property_changes', None) + self.fetch_snapshots = kwargs.get('fetch_snapshots', None) + + +class ResourceChangesRequestParametersInterval(DateTimeInterval): + """Specifies the date and time interval for a changes request. + + All required parameters must be populated in order to send to Azure. + + :param start: Required. A datetime indicating the inclusive/closed start of the time interval, + i.e. ``[``\ **\ ``start``\ **\ ``, end)``. Specifying a ``start`` that occurs chronologically + after ``end`` will result in an error. + :type start: ~datetime.datetime + :param end: Required. A datetime indicating the exclusive/open end of the time interval, i.e. + ``[start,``\ **\ ``end``\ **\ ``)``. Specifying an ``end`` that occurs chronologically before + ``start`` will result in an error. + :type end: ~datetime.datetime + """ + + _validation = { + 'start': {'required': True}, + 'end': {'required': True}, + } + + _attribute_map = { + 'start': {'key': 'start', 'type': 'iso-8601'}, + 'end': {'key': 'end', 'type': 'iso-8601'}, + } + + def __init__( + self, + **kwargs + ): + super(ResourceChangesRequestParametersInterval, self).__init__(**kwargs) + + +class ResourcePropertyChange(msrest.serialization.Model): + """The resource property change. + + All required parameters must be populated in order to send to Azure. + + :param property_name: Required. The property name. + :type property_name: str + :param before_value: The property value in before snapshot. + :type before_value: str + :param after_value: The property value in after snapshot. + :type after_value: str + :param change_category: Required. The change category. Possible values include: "User", + "System". + :type change_category: str or ~azure.mgmt.resourcegraph.models.ChangeCategory + :param property_change_type: Required. The property change Type. Possible values include: + "Insert", "Update", "Remove". + :type property_change_type: str or ~azure.mgmt.resourcegraph.models.PropertyChangeType + """ + + _validation = { + 'property_name': {'required': True}, + 'change_category': {'required': True}, + 'property_change_type': {'required': True}, + } + + _attribute_map = { + 'property_name': {'key': 'propertyName', 'type': 'str'}, + 'before_value': {'key': 'beforeValue', 'type': 'str'}, + 'after_value': {'key': 'afterValue', 'type': 'str'}, + 'change_category': {'key': 'changeCategory', 'type': 'str'}, + 'property_change_type': {'key': 'propertyChangeType', 'type': 'str'}, + } + + def __init__( + self, + **kwargs + ): + super(ResourcePropertyChange, self).__init__(**kwargs) + self.property_name = kwargs['property_name'] + self.before_value = kwargs.get('before_value', None) + self.after_value = kwargs.get('after_value', None) + self.change_category = kwargs['change_category'] + self.property_change_type = kwargs['property_change_type'] + + +class ResourcesHistoryRequest(msrest.serialization.Model): + """ResourcesHistoryRequest. + + :param subscriptions: + :type subscriptions: list[str] + :param query: + :type query: str + :param options: + :type options: ~azure.mgmt.resourcegraph.models.ResourcesHistoryRequestOptions + :param management_group_id: + :type management_group_id: str + """ + + _attribute_map = { + 'subscriptions': {'key': 'subscriptions', 'type': '[str]'}, + 'query': {'key': 'query', 'type': 'str'}, + 'options': {'key': 'options', 'type': 'ResourcesHistoryRequestOptions'}, + 'management_group_id': {'key': 'managementGroupId', 'type': 'str'}, + } + + def __init__( + self, + **kwargs + ): + super(ResourcesHistoryRequest, self).__init__(**kwargs) + self.subscriptions = kwargs.get('subscriptions', None) + self.query = kwargs.get('query', None) + self.options = kwargs.get('options', None) + self.management_group_id = kwargs.get('management_group_id', None) + + +class ResourcesHistoryRequestOptions(msrest.serialization.Model): + """ResourcesHistoryRequestOptions. + + :param interval: An interval in time specifying the date and time for the inclusive start and + exclusive end, i.e. ``[start, end)``. + :type interval: ~azure.mgmt.resourcegraph.models.DateTimeInterval + :param top: + :type top: int + :param skip: + :type skip: int + :param skip_token: + :type skip_token: str + :param result_format: Possible values include: "table", "objectArray". + :type result_format: str or + ~azure.mgmt.resourcegraph.models.ResourcesHistoryRequestOptionsResultFormat + """ + + _attribute_map = { + 'interval': {'key': 'interval', 'type': 'DateTimeInterval'}, + 'top': {'key': '$top', 'type': 'int'}, + 'skip': {'key': '$skip', 'type': 'int'}, + 'skip_token': {'key': '$skipToken', 'type': 'str'}, + 'result_format': {'key': 'resultFormat', 'type': 'str'}, + } + + def __init__( + self, + **kwargs + ): + super(ResourcesHistoryRequestOptions, self).__init__(**kwargs) + self.interval = kwargs.get('interval', None) + self.top = kwargs.get('top', None) + self.skip = kwargs.get('skip', None) + self.skip_token = kwargs.get('skip_token', None) + self.result_format = kwargs.get('result_format', None) + + +class Table(msrest.serialization.Model): + """Query output in tabular format. + + All required parameters must be populated in order to send to Azure. + + :param columns: Required. Query result column descriptors. + :type columns: list[~azure.mgmt.resourcegraph.models.Column] + :param rows: Required. Query result rows. + :type rows: list[list[object]] + """ + + _validation = { + 'columns': {'required': True}, + 'rows': {'required': True}, + } + + _attribute_map = { + 'columns': {'key': 'columns', 'type': '[Column]'}, + 'rows': {'key': 'rows', 'type': '[[object]]'}, + } + + def __init__( + self, + **kwargs + ): + super(Table, self).__init__(**kwargs) + self.columns = kwargs['columns'] + self.rows = kwargs['rows'] diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/models/_models_py3.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/models/_models_py3.py new file mode 100644 index 00000000000..1e01fe4052c --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/models/_models_py3.py @@ -0,0 +1,1390 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +import datetime +from typing import Dict, List, Optional, Union + +from azure.core.exceptions import HttpResponseError +import msrest.serialization + +from ._resource_graph_client_enums import * + + +class Column(msrest.serialization.Model): + """Query result column descriptor. + + All required parameters must be populated in order to send to Azure. + + :param name: Required. Column name. + :type name: str + :param type: Required. Column data type. Possible values include: "string", "integer", + "number", "boolean", "object". + :type type: str or ~azure.mgmt.resourcegraph.models.ColumnDataType + """ + + _validation = { + 'name': {'required': True}, + 'type': {'required': True}, + } + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__( + self, + *, + name: str, + type: Union[str, "ColumnDataType"], + **kwargs + ): + super(Column, self).__init__(**kwargs) + self.name = name + self.type = type + + +class DateTimeInterval(msrest.serialization.Model): + """An interval in time specifying the date and time for the inclusive start and exclusive end, i.e. ``[start, end)``. + + All required parameters must be populated in order to send to Azure. + + :param start: Required. A datetime indicating the inclusive/closed start of the time interval. + Specifying a ``start`` that occurs chronologically + after ``end`` will result in an error. + :type start: ~datetime.datetime + :param end: Required. A datetime indicating the exclusive/open end of the time interval. + Specifying an ``end`` that occurs chronologically before + ``start`` will result in an error. + :type end: ~datetime.datetime + """ + + _validation = { + 'start': {'required': True}, + 'end': {'required': True}, + } + + _attribute_map = { + 'start': {'key': 'start', 'type': 'iso-8601'}, + 'end': {'key': 'end', 'type': 'iso-8601'}, + } + + def __init__( + self, + *, + start: datetime.datetime, + end: datetime.datetime, + **kwargs + ): + super(DateTimeInterval, self).__init__(**kwargs) + self.start = start + self.end = end + + +class Error(msrest.serialization.Model): + """Error details. + + All required parameters must be populated in order to send to Azure. + + :param code: Required. Error code identifying the specific error. + :type code: str + :param message: Required. A human readable error message. + :type message: str + :param details: Error details. + :type details: list[~azure.mgmt.resourcegraph.models.ErrorDetails] + """ + + _validation = { + 'code': {'required': True}, + 'message': {'required': True}, + } + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'details': {'key': 'details', 'type': '[ErrorDetails]'}, + } + + def __init__( + self, + *, + code: str, + message: str, + details: Optional[List["ErrorDetails"]] = None, + **kwargs + ): + super(Error, self).__init__(**kwargs) + self.code = code + self.message = message + self.details = details + + +class ErrorDetails(msrest.serialization.Model): + """Error details. + + All required parameters must be populated in order to send to Azure. + + :param additional_properties: Unmatched properties from the message are deserialized to this + collection. + :type additional_properties: dict[str, object] + :param code: Required. Error code identifying the specific error. + :type code: str + :param message: Required. A human readable error message. + :type message: str + """ + + _validation = { + 'code': {'required': True}, + 'message': {'required': True}, + } + + _attribute_map = { + 'additional_properties': {'key': '', 'type': '{object}'}, + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + } + + def __init__( + self, + *, + code: str, + message: str, + additional_properties: Optional[Dict[str, object]] = None, + **kwargs + ): + super(ErrorDetails, self).__init__(**kwargs) + self.additional_properties = additional_properties + self.code = code + self.message = message + + +class ErrorFieldContract(msrest.serialization.Model): + """Error Field contract. + + :param code: Property level error code. + :type code: str + :param message: Human-readable representation of property-level error. + :type message: str + :param target: Property name. + :type target: str + """ + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'target': {'key': 'target', 'type': 'str'}, + } + + def __init__( + self, + *, + code: Optional[str] = None, + message: Optional[str] = None, + target: Optional[str] = None, + **kwargs + ): + super(ErrorFieldContract, self).__init__(**kwargs) + self.code = code + self.message = message + self.target = target + + +class ErrorResponse(msrest.serialization.Model): + """An error response from the API. + + All required parameters must be populated in order to send to Azure. + + :param error: Required. Error information. + :type error: ~azure.mgmt.resourcegraph.models.Error + """ + + _validation = { + 'error': {'required': True}, + } + + _attribute_map = { + 'error': {'key': 'error', 'type': 'Error'}, + } + + def __init__( + self, + *, + error: "Error", + **kwargs + ): + super(ErrorResponse, self).__init__(**kwargs) + self.error = error + + +class Facet(msrest.serialization.Model): + """A facet containing additional statistics on the response of a query. Can be either FacetResult or FacetError. + + You probably want to use the sub-classes and not this class directly. Known + sub-classes are: FacetError, FacetResult. + + All required parameters must be populated in order to send to Azure. + + :param expression: Required. Facet expression, same as in the corresponding facet request. + :type expression: str + :param result_type: Required. Result type.Constant filled by server. + :type result_type: str + """ + + _validation = { + 'expression': {'required': True}, + 'result_type': {'required': True}, + } + + _attribute_map = { + 'expression': {'key': 'expression', 'type': 'str'}, + 'result_type': {'key': 'resultType', 'type': 'str'}, + } + + _subtype_map = { + 'result_type': {'FacetError': 'FacetError', 'FacetResult': 'FacetResult'} + } + + def __init__( + self, + *, + expression: str, + **kwargs + ): + super(Facet, self).__init__(**kwargs) + self.expression = expression + self.result_type = None # type: Optional[str] + + +class FacetError(Facet): + """A facet whose execution resulted in an error. + + All required parameters must be populated in order to send to Azure. + + :param expression: Required. Facet expression, same as in the corresponding facet request. + :type expression: str + :param result_type: Required. Result type.Constant filled by server. + :type result_type: str + :param errors: Required. An array containing detected facet errors with details. + :type errors: list[~azure.mgmt.resourcegraph.models.ErrorDetails] + """ + + _validation = { + 'expression': {'required': True}, + 'result_type': {'required': True}, + 'errors': {'required': True}, + } + + _attribute_map = { + 'expression': {'key': 'expression', 'type': 'str'}, + 'result_type': {'key': 'resultType', 'type': 'str'}, + 'errors': {'key': 'errors', 'type': '[ErrorDetails]'}, + } + + def __init__( + self, + *, + expression: str, + errors: List["ErrorDetails"], + **kwargs + ): + super(FacetError, self).__init__(expression=expression, **kwargs) + self.result_type = 'FacetError' # type: str + self.errors = errors + + +class FacetRequest(msrest.serialization.Model): + """A request to compute additional statistics (facets) over the query results. + + All required parameters must be populated in order to send to Azure. + + :param expression: Required. The column or list of columns to summarize by. + :type expression: str + :param options: The options for facet evaluation. + :type options: ~azure.mgmt.resourcegraph.models.FacetRequestOptions + """ + + _validation = { + 'expression': {'required': True}, + } + + _attribute_map = { + 'expression': {'key': 'expression', 'type': 'str'}, + 'options': {'key': 'options', 'type': 'FacetRequestOptions'}, + } + + def __init__( + self, + *, + expression: str, + options: Optional["FacetRequestOptions"] = None, + **kwargs + ): + super(FacetRequest, self).__init__(**kwargs) + self.expression = expression + self.options = options + + +class FacetRequestOptions(msrest.serialization.Model): + """The options for facet evaluation. + + :param sort_by: The column name or query expression to sort on. Defaults to count if not + present. + :type sort_by: str + :param sort_order: The sorting order by the selected column (count by default). Possible values + include: "asc", "desc". Default value: "desc". + :type sort_order: str or ~azure.mgmt.resourcegraph.models.FacetSortOrder + :param filter: Specifies the filter condition for the 'where' clause which will be run on main + query's result, just before the actual faceting. + :type filter: str + :param top: The maximum number of facet rows that should be returned. + :type top: int + """ + + _validation = { + 'top': {'maximum': 1000, 'minimum': 1}, + } + + _attribute_map = { + 'sort_by': {'key': 'sortBy', 'type': 'str'}, + 'sort_order': {'key': 'sortOrder', 'type': 'str'}, + 'filter': {'key': 'filter', 'type': 'str'}, + 'top': {'key': '$top', 'type': 'int'}, + } + + def __init__( + self, + *, + sort_by: Optional[str] = None, + sort_order: Optional[Union[str, "FacetSortOrder"]] = "desc", + filter: Optional[str] = None, + top: Optional[int] = None, + **kwargs + ): + super(FacetRequestOptions, self).__init__(**kwargs) + self.sort_by = sort_by + self.sort_order = sort_order + self.filter = filter + self.top = top + + +class FacetResult(Facet): + """Successfully executed facet containing additional statistics on the response of a query. + + All required parameters must be populated in order to send to Azure. + + :param expression: Required. Facet expression, same as in the corresponding facet request. + :type expression: str + :param result_type: Required. Result type.Constant filled by server. + :type result_type: str + :param total_records: Required. Number of total records in the facet results. + :type total_records: long + :param count: Required. Number of records returned in the facet response. + :type count: int + :param data: Required. A JObject array or Table containing the desired facets. Only present if + the facet is valid. + :type data: object + """ + + _validation = { + 'expression': {'required': True}, + 'result_type': {'required': True}, + 'total_records': {'required': True}, + 'count': {'required': True}, + 'data': {'required': True}, + } + + _attribute_map = { + 'expression': {'key': 'expression', 'type': 'str'}, + 'result_type': {'key': 'resultType', 'type': 'str'}, + 'total_records': {'key': 'totalRecords', 'type': 'long'}, + 'count': {'key': 'count', 'type': 'int'}, + 'data': {'key': 'data', 'type': 'object'}, + } + + def __init__( + self, + *, + expression: str, + total_records: int, + count: int, + data: object, + **kwargs + ): + super(FacetResult, self).__init__(expression=expression, **kwargs) + self.result_type = 'FacetResult' # type: str + self.total_records = total_records + self.count = count + self.data = data + + +class GraphQueryError(msrest.serialization.Model): + """Error message body that will indicate why the operation failed. + + :param code: Service-defined error code. This code serves as a sub-status for the HTTP error + code specified in the response. + :type code: str + :param message: Human-readable representation of the error. + :type message: str + :param details: The list of invalid fields send in request, in case of validation error. + :type details: list[~azure.mgmt.resourcegraph.models.ErrorFieldContract] + """ + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'details': {'key': 'details', 'type': '[ErrorFieldContract]'}, + } + + def __init__( + self, + *, + code: Optional[str] = None, + message: Optional[str] = None, + details: Optional[List["ErrorFieldContract"]] = None, + **kwargs + ): + super(GraphQueryError, self).__init__(**kwargs) + self.code = code + self.message = message + self.details = details + + +class GraphQueryListResult(msrest.serialization.Model): + """Graph query list result. + + Variables are only populated by the server, and will be ignored when sending a request. + + :param next_link: URL to fetch the next set of queries. + :type next_link: str + :ivar value: An array of graph queries. + :vartype value: list[~azure.mgmt.resourcegraph.models.GraphQueryResource] + """ + + _validation = { + 'value': {'readonly': True}, + } + + _attribute_map = { + 'next_link': {'key': 'nextLink', 'type': 'str'}, + 'value': {'key': 'value', 'type': '[GraphQueryResource]'}, + } + + def __init__( + self, + *, + next_link: Optional[str] = None, + **kwargs + ): + super(GraphQueryListResult, self).__init__(**kwargs) + self.next_link = next_link + self.value = None + + +class Resource(msrest.serialization.Model): + """An azure resource object. + + Variables are only populated by the server, and will be ignored when sending a request. + + :ivar id: Azure resource Id. + :vartype id: str + :ivar name: Azure resource name. This is GUID value. The display name should be assigned within + properties field. + :vartype name: str + :param location: The location of the resource. + :type location: str + :ivar type: Azure resource type. + :vartype type: str + :param etag: This will be used to handle Optimistic Concurrency. If not present, it will always + overwrite the existing resource without checking conflict. + :type etag: str + :param tags: A set of tags. Resource tags. + :type tags: dict[str, str] + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True}, + 'type': {'readonly': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'location': {'key': 'location', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + 'etag': {'key': 'etag', 'type': 'str'}, + 'tags': {'key': 'tags', 'type': '{str}'}, + } + + def __init__( + self, + *, + location: Optional[str] = None, + etag: Optional[str] = None, + tags: Optional[Dict[str, str]] = None, + **kwargs + ): + super(Resource, self).__init__(**kwargs) + self.id = None + self.name = None + self.location = location + self.type = None + self.etag = etag + self.tags = tags + + +class GraphQueryResource(Resource): + """Graph Query entity definition. + + Variables are only populated by the server, and will be ignored when sending a request. + + :ivar id: Azure resource Id. + :vartype id: str + :ivar name: Azure resource name. This is GUID value. The display name should be assigned within + properties field. + :vartype name: str + :param location: The location of the resource. + :type location: str + :ivar type: Azure resource type. + :vartype type: str + :param etag: This will be used to handle Optimistic Concurrency. If not present, it will always + overwrite the existing resource without checking conflict. + :type etag: str + :param tags: A set of tags. Resource tags. + :type tags: dict[str, str] + :ivar time_modified: Date and time in UTC of the last modification that was made to this graph + query definition. + :vartype time_modified: ~datetime.datetime + :param description: The description of a graph query. + :type description: str + :param query: KQL query that will be graph. + :type query: str + :ivar result_kind: Enum indicating a type of graph query. Possible values include: "basic". + :vartype result_kind: str or ~azure.mgmt.resourcegraph.models.ResultKind + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True}, + 'type': {'readonly': True}, + 'time_modified': {'readonly': True}, + 'result_kind': {'readonly': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'location': {'key': 'location', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + 'etag': {'key': 'etag', 'type': 'str'}, + 'tags': {'key': 'tags', 'type': '{str}'}, + 'time_modified': {'key': 'properties.timeModified', 'type': 'iso-8601'}, + 'description': {'key': 'properties.description', 'type': 'str'}, + 'query': {'key': 'properties.query', 'type': 'str'}, + 'result_kind': {'key': 'properties.resultKind', 'type': 'str'}, + } + + def __init__( + self, + *, + location: Optional[str] = None, + etag: Optional[str] = None, + tags: Optional[Dict[str, str]] = None, + description: Optional[str] = None, + query: Optional[str] = None, + **kwargs + ): + super(GraphQueryResource, self).__init__(location=location, etag=etag, tags=tags, **kwargs) + self.time_modified = None + self.description = description + self.query = query + self.result_kind = None + + +class GraphQueryUpdateParameters(msrest.serialization.Model): + """The parameters that can be provided when updating workbook properties properties. + + :param tags: A set of tags. Resource tags. + :type tags: dict[str, str] + :param etag: This will be used to handle Optimistic Concurrency. If not present, it will always + overwrite the existing resource without checking conflict. + :type etag: str + :param description: The description of a graph query. + :type description: str + :param query: KQL query that will be graph. + :type query: str + """ + + _attribute_map = { + 'tags': {'key': 'tags', 'type': '{str}'}, + 'etag': {'key': 'etag', 'type': 'str'}, + 'description': {'key': 'properties.description', 'type': 'str'}, + 'query': {'key': 'properties.query', 'type': 'str'}, + } + + def __init__( + self, + *, + tags: Optional[Dict[str, str]] = None, + etag: Optional[str] = None, + description: Optional[str] = None, + query: Optional[str] = None, + **kwargs + ): + super(GraphQueryUpdateParameters, self).__init__(**kwargs) + self.tags = tags + self.etag = etag + self.description = description + self.query = query + + +class Operation(msrest.serialization.Model): + """Resource Graph REST API operation definition. + + :param name: Operation name: {provider}/{resource}/{operation}. + :type name: str + :param display: Display metadata associated with the operation. + :type display: ~azure.mgmt.resourcegraph.models.OperationDisplay + :param origin: The origin of operations. + :type origin: str + """ + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'display': {'key': 'display', 'type': 'OperationDisplay'}, + 'origin': {'key': 'origin', 'type': 'str'}, + } + + def __init__( + self, + *, + name: Optional[str] = None, + display: Optional["OperationDisplay"] = None, + origin: Optional[str] = None, + **kwargs + ): + super(Operation, self).__init__(**kwargs) + self.name = name + self.display = display + self.origin = origin + + +class OperationDisplay(msrest.serialization.Model): + """Display metadata associated with the operation. + + :param provider: Service provider: Microsoft Resource Graph. + :type provider: str + :param resource: Resource on which the operation is performed etc. + :type resource: str + :param operation: Type of operation: get, read, delete, etc. + :type operation: str + :param description: Description for the operation. + :type description: str + """ + + _attribute_map = { + 'provider': {'key': 'provider', 'type': 'str'}, + 'resource': {'key': 'resource', 'type': 'str'}, + 'operation': {'key': 'operation', 'type': 'str'}, + 'description': {'key': 'description', 'type': 'str'}, + } + + def __init__( + self, + *, + provider: Optional[str] = None, + resource: Optional[str] = None, + operation: Optional[str] = None, + description: Optional[str] = None, + **kwargs + ): + super(OperationDisplay, self).__init__(**kwargs) + self.provider = provider + self.resource = resource + self.operation = operation + self.description = description + + +class OperationListResult(msrest.serialization.Model): + """Result of the request to list Resource Graph operations. It contains a list of operations and a URL link to get the next set of results. + + :param value: List of Resource Graph operations supported by the Resource Graph resource + provider. + :type value: list[~azure.mgmt.resourcegraph.models.Operation] + """ + + _attribute_map = { + 'value': {'key': 'value', 'type': '[Operation]'}, + } + + def __init__( + self, + *, + value: Optional[List["Operation"]] = None, + **kwargs + ): + super(OperationListResult, self).__init__(**kwargs) + self.value = value + + +class QueryRequest(msrest.serialization.Model): + """Describes a query to be executed. + + All required parameters must be populated in order to send to Azure. + + :param subscriptions: Azure subscriptions against which to execute the query. + :type subscriptions: list[str] + :param management_groups: Azure management groups against which to execute the query. Example: + [ 'mg1', 'mg2' ]. + :type management_groups: list[str] + :param query: Required. The resources query. + :type query: str + :param options: The query evaluation options. + :type options: ~azure.mgmt.resourcegraph.models.QueryRequestOptions + :param facets: An array of facet requests to be computed against the query result. + :type facets: list[~azure.mgmt.resourcegraph.models.FacetRequest] + """ + + _validation = { + 'query': {'required': True}, + } + + _attribute_map = { + 'subscriptions': {'key': 'subscriptions', 'type': '[str]'}, + 'management_groups': {'key': 'managementGroups', 'type': '[str]'}, + 'query': {'key': 'query', 'type': 'str'}, + 'options': {'key': 'options', 'type': 'QueryRequestOptions'}, + 'facets': {'key': 'facets', 'type': '[FacetRequest]'}, + } + + def __init__( + self, + *, + query: str, + subscriptions: Optional[List[str]] = None, + management_groups: Optional[List[str]] = None, + options: Optional["QueryRequestOptions"] = None, + facets: Optional[List["FacetRequest"]] = None, + **kwargs + ): + super(QueryRequest, self).__init__(**kwargs) + self.subscriptions = subscriptions + self.management_groups = management_groups + self.query = query + self.options = options + self.facets = facets + + +class QueryRequestOptions(msrest.serialization.Model): + """The options for query evaluation. + + :param skip_token: Continuation token for pagination, capturing the next page size and offset, + as well as the context of the query. + :type skip_token: str + :param top: The maximum number of rows that the query should return. Overrides the page size + when ``$skipToken`` property is present. + :type top: int + :param skip: The number of rows to skip from the beginning of the results. Overrides the next + page offset when ``$skipToken`` property is present. + :type skip: int + :param result_format: Defines in which format query result returned. Possible values include: + "table", "objectArray". Default value: "objectArray". + :type result_format: str or ~azure.mgmt.resourcegraph.models.ResultFormat + :param allow_partial_scopes: Only applicable for tenant and management group level queries to + decide whether to allow partial scopes for result in case the number of subscriptions exceed + allowed limits. + :type allow_partial_scopes: bool + """ + + _validation = { + 'top': {'maximum': 1000, 'minimum': 1}, + 'skip': {'minimum': 0}, + } + + _attribute_map = { + 'skip_token': {'key': '$skipToken', 'type': 'str'}, + 'top': {'key': '$top', 'type': 'int'}, + 'skip': {'key': '$skip', 'type': 'int'}, + 'result_format': {'key': 'resultFormat', 'type': 'str'}, + 'allow_partial_scopes': {'key': 'allowPartialScopes', 'type': 'bool'}, + } + + def __init__( + self, + *, + skip_token: Optional[str] = None, + top: Optional[int] = None, + skip: Optional[int] = None, + result_format: Optional[Union[str, "ResultFormat"]] = "objectArray", + allow_partial_scopes: Optional[bool] = False, + **kwargs + ): + super(QueryRequestOptions, self).__init__(**kwargs) + self.skip_token = skip_token + self.top = top + self.skip = skip + self.result_format = result_format + self.allow_partial_scopes = allow_partial_scopes + + +class QueryResponse(msrest.serialization.Model): + """Query result. + + All required parameters must be populated in order to send to Azure. + + :param total_records: Required. Number of total records matching the query. + :type total_records: long + :param count: Required. Number of records returned in the current response. In the case of + paging, this is the number of records in the current page. + :type count: long + :param result_truncated: Required. Indicates whether the query results are truncated. Possible + values include: "true", "false". + :type result_truncated: str or ~azure.mgmt.resourcegraph.models.ResultTruncated + :param skip_token: When present, the value can be passed to a subsequent query call (together + with the same query and scopes used in the current request) to retrieve the next page of data. + :type skip_token: str + :param data: Required. Query output in JObject array or Table format. + :type data: object + :param facets: Query facets. + :type facets: list[~azure.mgmt.resourcegraph.models.Facet] + """ + + _validation = { + 'total_records': {'required': True}, + 'count': {'required': True}, + 'result_truncated': {'required': True}, + 'data': {'required': True}, + } + + _attribute_map = { + 'total_records': {'key': 'totalRecords', 'type': 'long'}, + 'count': {'key': 'count', 'type': 'long'}, + 'result_truncated': {'key': 'resultTruncated', 'type': 'str'}, + 'skip_token': {'key': '$skipToken', 'type': 'str'}, + 'data': {'key': 'data', 'type': 'object'}, + 'facets': {'key': 'facets', 'type': '[Facet]'}, + } + + def __init__( + self, + *, + total_records: int, + count: int, + result_truncated: Union[str, "ResultTruncated"], + data: object, + skip_token: Optional[str] = None, + facets: Optional[List["Facet"]] = None, + **kwargs + ): + super(QueryResponse, self).__init__(**kwargs) + self.total_records = total_records + self.count = count + self.result_truncated = result_truncated + self.skip_token = skip_token + self.data = data + self.facets = facets + + +class ResourceChangeData(msrest.serialization.Model): + """Data on a specific change, represented by a pair of before and after resource snapshots. + + All required parameters must be populated in order to send to Azure. + + :param resource_id: The resource for a change. + :type resource_id: str + :param change_id: Required. The change ID. Valid and unique within the specified resource only. + :type change_id: str + :param before_snapshot: Required. The snapshot before the change. + :type before_snapshot: ~azure.mgmt.resourcegraph.models.ResourceSnapshotData + :param after_snapshot: Required. The snapshot after the change. + :type after_snapshot: ~azure.mgmt.resourcegraph.models.ResourceSnapshotData + :param change_type: The change type for snapshot. PropertyChanges will be provided in case of + Update change type. Possible values include: "Create", "Update", "Delete". + :type change_type: str or ~azure.mgmt.resourcegraph.models.ChangeType + :param property_changes: An array of resource property change. + :type property_changes: list[~azure.mgmt.resourcegraph.models.ResourcePropertyChange] + """ + + _validation = { + 'change_id': {'required': True}, + 'before_snapshot': {'required': True}, + 'after_snapshot': {'required': True}, + } + + _attribute_map = { + 'resource_id': {'key': 'resourceId', 'type': 'str'}, + 'change_id': {'key': 'changeId', 'type': 'str'}, + 'before_snapshot': {'key': 'beforeSnapshot', 'type': 'ResourceSnapshotData'}, + 'after_snapshot': {'key': 'afterSnapshot', 'type': 'ResourceSnapshotData'}, + 'change_type': {'key': 'changeType', 'type': 'str'}, + 'property_changes': {'key': 'propertyChanges', 'type': '[ResourcePropertyChange]'}, + } + + def __init__( + self, + *, + change_id: str, + before_snapshot: "ResourceSnapshotData", + after_snapshot: "ResourceSnapshotData", + resource_id: Optional[str] = None, + change_type: Optional[Union[str, "ChangeType"]] = None, + property_changes: Optional[List["ResourcePropertyChange"]] = None, + **kwargs + ): + super(ResourceChangeData, self).__init__(**kwargs) + self.resource_id = resource_id + self.change_id = change_id + self.before_snapshot = before_snapshot + self.after_snapshot = after_snapshot + self.change_type = change_type + self.property_changes = property_changes + + +class ResourceSnapshotData(msrest.serialization.Model): + """Data on a specific resource snapshot. + + All required parameters must be populated in order to send to Azure. + + :param snapshot_id: The ID of the snapshot. + :type snapshot_id: str + :param timestamp: Required. The time when the snapshot was created. + The snapshot timestamp provides an approximation as to when a modification to a resource was + detected. There can be a difference between the actual modification time and the detection + time. This is due to differences in how operations that modify a resource are processed, + versus how operation that record resource snapshots are processed. + :type timestamp: ~datetime.datetime + :param content: The resource snapshot content (in resourceChangeDetails response only). + :type content: object + """ + + _validation = { + 'timestamp': {'required': True}, + } + + _attribute_map = { + 'snapshot_id': {'key': 'snapshotId', 'type': 'str'}, + 'timestamp': {'key': 'timestamp', 'type': 'iso-8601'}, + 'content': {'key': 'content', 'type': 'object'}, + } + + def __init__( + self, + *, + timestamp: datetime.datetime, + snapshot_id: Optional[str] = None, + content: Optional[object] = None, + **kwargs + ): + super(ResourceSnapshotData, self).__init__(**kwargs) + self.snapshot_id = snapshot_id + self.timestamp = timestamp + self.content = content + + +class ResourceChangeDataAfterSnapshot(ResourceSnapshotData): + """The snapshot after the change. + + All required parameters must be populated in order to send to Azure. + + :param snapshot_id: The ID of the snapshot. + :type snapshot_id: str + :param timestamp: Required. The time when the snapshot was created. + The snapshot timestamp provides an approximation as to when a modification to a resource was + detected. There can be a difference between the actual modification time and the detection + time. This is due to differences in how operations that modify a resource are processed, + versus how operation that record resource snapshots are processed. + :type timestamp: ~datetime.datetime + :param content: The resource snapshot content (in resourceChangeDetails response only). + :type content: object + """ + + _validation = { + 'timestamp': {'required': True}, + } + + _attribute_map = { + 'snapshot_id': {'key': 'snapshotId', 'type': 'str'}, + 'timestamp': {'key': 'timestamp', 'type': 'iso-8601'}, + 'content': {'key': 'content', 'type': 'object'}, + } + + def __init__( + self, + *, + timestamp: datetime.datetime, + snapshot_id: Optional[str] = None, + content: Optional[object] = None, + **kwargs + ): + super(ResourceChangeDataAfterSnapshot, self).__init__(snapshot_id=snapshot_id, timestamp=timestamp, content=content, **kwargs) + + +class ResourceChangeDataBeforeSnapshot(ResourceSnapshotData): + """The snapshot before the change. + + All required parameters must be populated in order to send to Azure. + + :param snapshot_id: The ID of the snapshot. + :type snapshot_id: str + :param timestamp: Required. The time when the snapshot was created. + The snapshot timestamp provides an approximation as to when a modification to a resource was + detected. There can be a difference between the actual modification time and the detection + time. This is due to differences in how operations that modify a resource are processed, + versus how operation that record resource snapshots are processed. + :type timestamp: ~datetime.datetime + :param content: The resource snapshot content (in resourceChangeDetails response only). + :type content: object + """ + + _validation = { + 'timestamp': {'required': True}, + } + + _attribute_map = { + 'snapshot_id': {'key': 'snapshotId', 'type': 'str'}, + 'timestamp': {'key': 'timestamp', 'type': 'iso-8601'}, + 'content': {'key': 'content', 'type': 'object'}, + } + + def __init__( + self, + *, + timestamp: datetime.datetime, + snapshot_id: Optional[str] = None, + content: Optional[object] = None, + **kwargs + ): + super(ResourceChangeDataBeforeSnapshot, self).__init__(snapshot_id=snapshot_id, timestamp=timestamp, content=content, **kwargs) + + +class ResourceChangeDetailsRequestParameters(msrest.serialization.Model): + """The parameters for a specific change details request. + + All required parameters must be populated in order to send to Azure. + + :param resource_ids: Required. Specifies the list of resources for a change details request. + :type resource_ids: list[str] + :param change_ids: Required. Specifies the list of change IDs for a change details request. + :type change_ids: list[str] + """ + + _validation = { + 'resource_ids': {'required': True}, + 'change_ids': {'required': True}, + } + + _attribute_map = { + 'resource_ids': {'key': 'resourceIds', 'type': '[str]'}, + 'change_ids': {'key': 'changeIds', 'type': '[str]'}, + } + + def __init__( + self, + *, + resource_ids: List[str], + change_ids: List[str], + **kwargs + ): + super(ResourceChangeDetailsRequestParameters, self).__init__(**kwargs) + self.resource_ids = resource_ids + self.change_ids = change_ids + + +class ResourceChangeList(msrest.serialization.Model): + """A list of changes associated with a resource over a specific time interval. + + :param changes: The pageable value returned by the operation, i.e. a list of changes to the + resource. + + + * The list is ordered from the most recent changes to the least recent changes. + * This list will be empty if there were no changes during the requested interval. + * The ``Before`` snapshot timestamp value of the oldest change can be outside of the specified + time interval. + :type changes: list[~azure.mgmt.resourcegraph.models.ResourceChangeData] + :param skip_token: Skip token that encodes the skip information while executing the current + request. + :type skip_token: object + """ + + _attribute_map = { + 'changes': {'key': 'changes', 'type': '[ResourceChangeData]'}, + 'skip_token': {'key': '$skipToken', 'type': 'object'}, + } + + def __init__( + self, + *, + changes: Optional[List["ResourceChangeData"]] = None, + skip_token: Optional[object] = None, + **kwargs + ): + super(ResourceChangeList, self).__init__(**kwargs) + self.changes = changes + self.skip_token = skip_token + + +class ResourceChangesRequestParameters(msrest.serialization.Model): + """The parameters for a specific changes request. + + All required parameters must be populated in order to send to Azure. + + :param resource_ids: Specifies the list of resources for a changes request. + :type resource_ids: list[str] + :param subscription_id: The subscription id of resources to query the changes from. + :type subscription_id: str + :param interval: Required. Specifies the date and time interval for a changes request. + :type interval: ~azure.mgmt.resourcegraph.models.DateTimeInterval + :param skip_token: Acts as the continuation token for paged responses. + :type skip_token: str + :param top: The maximum number of changes the client can accept in a paged response. + :type top: int + :param table: The table name to query resources from. + :type table: str + :param fetch_property_changes: The flag if set to true will fetch property changes. + :type fetch_property_changes: bool + :param fetch_snapshots: The flag if set to true will fetch change snapshots. + :type fetch_snapshots: bool + """ + + _validation = { + 'interval': {'required': True}, + 'top': {'maximum': 1000, 'minimum': 1}, + } + + _attribute_map = { + 'resource_ids': {'key': 'resourceIds', 'type': '[str]'}, + 'subscription_id': {'key': 'subscriptionId', 'type': 'str'}, + 'interval': {'key': 'interval', 'type': 'DateTimeInterval'}, + 'skip_token': {'key': '$skipToken', 'type': 'str'}, + 'top': {'key': '$top', 'type': 'int'}, + 'table': {'key': 'table', 'type': 'str'}, + 'fetch_property_changes': {'key': 'fetchPropertyChanges', 'type': 'bool'}, + 'fetch_snapshots': {'key': 'fetchSnapshots', 'type': 'bool'}, + } + + def __init__( + self, + *, + interval: "DateTimeInterval", + resource_ids: Optional[List[str]] = None, + subscription_id: Optional[str] = None, + skip_token: Optional[str] = None, + top: Optional[int] = None, + table: Optional[str] = None, + fetch_property_changes: Optional[bool] = None, + fetch_snapshots: Optional[bool] = None, + **kwargs + ): + super(ResourceChangesRequestParameters, self).__init__(**kwargs) + self.resource_ids = resource_ids + self.subscription_id = subscription_id + self.interval = interval + self.skip_token = skip_token + self.top = top + self.table = table + self.fetch_property_changes = fetch_property_changes + self.fetch_snapshots = fetch_snapshots + + +class ResourceChangesRequestParametersInterval(DateTimeInterval): + """Specifies the date and time interval for a changes request. + + All required parameters must be populated in order to send to Azure. + + :param start: Required. A datetime indicating the inclusive/closed start of the time interval. + Specifying a ``start`` that occurs chronologically + after ``end`` will result in an error. + :type start: ~datetime.datetime + :param end: Required. A datetime indicating the exclusive/open end of the time interval. + Specifying an ``end`` that occurs chronologically before + ``start`` will result in an error. + :type end: ~datetime.datetime + """ + + _validation = { + 'start': {'required': True}, + 'end': {'required': True}, + } + + _attribute_map = { + 'start': {'key': 'start', 'type': 'iso-8601'}, + 'end': {'key': 'end', 'type': 'iso-8601'}, + } + + def __init__( + self, + *, + start: datetime.datetime, + end: datetime.datetime, + **kwargs + ): + super(ResourceChangesRequestParametersInterval, self).__init__(start=start, end=end, **kwargs) + + +class ResourcePropertyChange(msrest.serialization.Model): + """The resource property change. + + All required parameters must be populated in order to send to Azure. + + :param property_name: Required. The property name. + :type property_name: str + :param before_value: The property value in before snapshot. + :type before_value: str + :param after_value: The property value in after snapshot. + :type after_value: str + :param change_category: Required. The change category. Possible values include: "User", + "System". + :type change_category: str or ~azure.mgmt.resourcegraph.models.ChangeCategory + :param property_change_type: Required. The property change Type. Possible values include: + "Insert", "Update", "Remove". + :type property_change_type: str or ~azure.mgmt.resourcegraph.models.PropertyChangeType + """ + + _validation = { + 'property_name': {'required': True}, + 'change_category': {'required': True}, + 'property_change_type': {'required': True}, + } + + _attribute_map = { + 'property_name': {'key': 'propertyName', 'type': 'str'}, + 'before_value': {'key': 'beforeValue', 'type': 'str'}, + 'after_value': {'key': 'afterValue', 'type': 'str'}, + 'change_category': {'key': 'changeCategory', 'type': 'str'}, + 'property_change_type': {'key': 'propertyChangeType', 'type': 'str'}, + } + + def __init__( + self, + *, + property_name: str, + change_category: Union[str, "ChangeCategory"], + property_change_type: Union[str, "PropertyChangeType"], + before_value: Optional[str] = None, + after_value: Optional[str] = None, + **kwargs + ): + super(ResourcePropertyChange, self).__init__(**kwargs) + self.property_name = property_name + self.before_value = before_value + self.after_value = after_value + self.change_category = change_category + self.property_change_type = property_change_type + + +class ResourcesHistoryRequest(msrest.serialization.Model): + """ResourcesHistoryRequest. + + :param subscriptions: + :type subscriptions: list[str] + :param query: + :type query: str + :param options: + :type options: ~azure.mgmt.resourcegraph.models.ResourcesHistoryRequestOptions + :param management_group_id: + :type management_group_id: str + """ + + _attribute_map = { + 'subscriptions': {'key': 'subscriptions', 'type': '[str]'}, + 'query': {'key': 'query', 'type': 'str'}, + 'options': {'key': 'options', 'type': 'ResourcesHistoryRequestOptions'}, + 'management_group_id': {'key': 'managementGroupId', 'type': 'str'}, + } + + def __init__( + self, + *, + subscriptions: Optional[List[str]] = None, + query: Optional[str] = None, + options: Optional["ResourcesHistoryRequestOptions"] = None, + management_group_id: Optional[str] = None, + **kwargs + ): + super(ResourcesHistoryRequest, self).__init__(**kwargs) + self.subscriptions = subscriptions + self.query = query + self.options = options + self.management_group_id = management_group_id + + +class ResourcesHistoryRequestOptions(msrest.serialization.Model): + """ResourcesHistoryRequestOptions. + + :param interval: An interval in time specifying the date and time for the inclusive start and + exclusive end, i.e. ``[start, end)``. + :type interval: ~azure.mgmt.resourcegraph.models.DateTimeInterval + :param top: + :type top: int + :param skip: + :type skip: int + :param skip_token: + :type skip_token: str + :param result_format: Possible values include: "table", "objectArray". + :type result_format: str or + ~azure.mgmt.resourcegraph.models.ResourcesHistoryRequestOptionsResultFormat + """ + + _attribute_map = { + 'interval': {'key': 'interval', 'type': 'DateTimeInterval'}, + 'top': {'key': '$top', 'type': 'int'}, + 'skip': {'key': '$skip', 'type': 'int'}, + 'skip_token': {'key': '$skipToken', 'type': 'str'}, + 'result_format': {'key': 'resultFormat', 'type': 'str'}, + } + + def __init__( + self, + *, + interval: Optional["DateTimeInterval"] = None, + top: Optional[int] = None, + skip: Optional[int] = None, + skip_token: Optional[str] = None, + result_format: Optional[Union[str, "ResourcesHistoryRequestOptionsResultFormat"]] = None, + **kwargs + ): + super(ResourcesHistoryRequestOptions, self).__init__(**kwargs) + self.interval = interval + self.top = top + self.skip = skip + self.skip_token = skip_token + self.result_format = result_format + + +class Table(msrest.serialization.Model): + """Query output in tabular format. + + All required parameters must be populated in order to send to Azure. + + :param columns: Required. Query result column descriptors. + :type columns: list[~azure.mgmt.resourcegraph.models.Column] + :param rows: Required. Query result rows. + :type rows: list[list[object]] + """ + + _validation = { + 'columns': {'required': True}, + 'rows': {'required': True}, + } + + _attribute_map = { + 'columns': {'key': 'columns', 'type': '[Column]'}, + 'rows': {'key': 'rows', 'type': '[[object]]'}, + } + + def __init__( + self, + *, + columns: List["Column"], + rows: List[List[object]], + **kwargs + ): + super(Table, self).__init__(**kwargs) + self.columns = columns + self.rows = rows diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/models/_resource_graph_client_enums.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/models/_resource_graph_client_enums.py new file mode 100644 index 00000000000..917bea73469 --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/models/_resource_graph_client_enums.py @@ -0,0 +1,92 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from enum import Enum, EnumMeta +from six import with_metaclass + +class _CaseInsensitiveEnumMeta(EnumMeta): + def __getitem__(self, name): + return super().__getitem__(name.upper()) + + def __getattr__(cls, name): + """Return the enum member matching `name` + We use __getattr__ instead of descriptors or inserting into the enum + class' __dict__ in order to support `name` and `value` being both + properties for enum members (which live in the class' __dict__) and + enum members themselves. + """ + try: + return cls._member_map_[name.upper()] + except KeyError: + raise AttributeError(name) + + +class ChangeCategory(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): + """The change category. + """ + + USER = "User" + SYSTEM = "System" + +class ChangeType(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): + """The change type for snapshot. PropertyChanges will be provided in case of Update change type + """ + + CREATE = "Create" + UPDATE = "Update" + DELETE = "Delete" + +class ColumnDataType(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): + """Data type of a column in a table. + """ + + STRING = "string" + INTEGER = "integer" + NUMBER = "number" + BOOLEAN = "boolean" + OBJECT = "object" + +class FacetSortOrder(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): + """The sorting order by the selected column (count by default). + """ + + ASC = "asc" + DESC = "desc" + +class PropertyChangeType(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): + """The property change Type + """ + + INSERT = "Insert" + UPDATE = "Update" + REMOVE = "Remove" + +class ResourcesHistoryRequestOptionsResultFormat(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): + + TABLE = "table" + OBJECT_ARRAY = "objectArray" + +class ResultFormat(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): + """Defines in which format query result returned. + """ + + TABLE = "table" + OBJECT_ARRAY = "objectArray" + +class ResultKind(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): + """Enum indicating a type of graph query. + """ + + BASIC = "basic" + +class ResultTruncated(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): + """Indicates whether the query results are truncated. + """ + + TRUE = "true" + FALSE = "false" diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/operations/__init__.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/operations/__init__.py new file mode 100644 index 00000000000..21a67ce4dae --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/operations/__init__.py @@ -0,0 +1,17 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from ._resource_graph_client_operations import ResourceGraphClientOperationsMixin +from ._operations import Operations +from ._graph_query_operations import GraphQueryOperations + +__all__ = [ + 'ResourceGraphClientOperationsMixin', + 'Operations', + 'GraphQueryOperations', +] diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/operations/_graph_query_operations.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/operations/_graph_query_operations.py new file mode 100644 index 00000000000..59f6ed9dc07 --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/operations/_graph_query_operations.py @@ -0,0 +1,371 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +from typing import TYPE_CHECKING +import warnings + +from azure.core.exceptions import ClientAuthenticationError, HttpResponseError, ResourceExistsError, ResourceNotFoundError, map_error +from azure.core.paging import ItemPaged +from azure.core.pipeline import PipelineResponse +from azure.core.pipeline.transport import HttpRequest, HttpResponse +from azure.mgmt.core.exceptions import ARMErrorFormat + +from .. import models as _models + +if TYPE_CHECKING: + # pylint: disable=unused-import,ungrouped-imports + from typing import Any, Callable, Dict, Generic, Iterable, Optional, TypeVar + + T = TypeVar('T') + ClsType = Optional[Callable[[PipelineResponse[HttpRequest, HttpResponse], T, Dict[str, Any]], Any]] + +class GraphQueryOperations(object): + """GraphQueryOperations operations. + + You should not instantiate this class directly. Instead, you should create a Client instance that + instantiates it for you and attaches it as an attribute. + + :ivar models: Alias to model classes used in this operation group. + :type models: ~azure.mgmt.resourcegraph.models + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + """ + + models = _models + + def __init__(self, client, config, serializer, deserializer): + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self._config = config + + def list( + self, + resource_group_name, # type: str + **kwargs # type: Any + ): + # type: (...) -> Iterable["_models.GraphQueryListResult"] + """Get all graph queries defined within a specified subscription and resource group. + + :param resource_group_name: The name of the resource group. + :type resource_group_name: str + :keyword callable cls: A custom type or function that will be passed the direct response + :return: An iterator like instance of either GraphQueryListResult or the result of cls(response) + :rtype: ~azure.core.paging.ItemPaged[~azure.mgmt.resourcegraph.models.GraphQueryListResult] + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.GraphQueryListResult"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2018-09-01-preview" + accept = "application/json" + + def prepare_request(next_link=None): + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + if not next_link: + # Construct URL + url = self.list.metadata['url'] # type: ignore + path_format_arguments = { + 'subscriptionId': self._serialize.url("self._config.subscription_id", self._config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str'), + } + url = self._client.format_url(url, **path_format_arguments) + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + request = self._client.get(url, query_parameters, header_parameters) + else: + url = next_link + query_parameters = {} # type: Dict[str, Any] + request = self._client.get(url, query_parameters, header_parameters) + return request + + def extract_data(pipeline_response): + deserialized = self._deserialize('GraphQueryListResult', pipeline_response) + list_of_elem = deserialized.value + if cls: + list_of_elem = cls(list_of_elem) + return deserialized.next_link or None, iter(list_of_elem) + + def get_next(next_link=None): + request = prepare_request(next_link) + + pipeline_response = self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + error = self._deserialize(_models.GraphQueryError, response) + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + return pipeline_response + + return ItemPaged( + get_next, extract_data + ) + list.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ResourceGraph/queries'} # type: ignore + + def get( + self, + resource_group_name, # type: str + resource_name, # type: str + **kwargs # type: Any + ): + # type: (...) -> "_models.GraphQueryResource" + """Get a single graph query by its resourceName. + + :param resource_group_name: The name of the resource group. + :type resource_group_name: str + :param resource_name: The name of the Graph Query resource. + :type resource_name: str + :keyword callable cls: A custom type or function that will be passed the direct response + :return: GraphQueryResource, or the result of cls(response) + :rtype: ~azure.mgmt.resourcegraph.models.GraphQueryResource + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.GraphQueryResource"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2018-09-01-preview" + accept = "application/json" + + # Construct URL + url = self.get.metadata['url'] # type: ignore + path_format_arguments = { + 'subscriptionId': self._serialize.url("self._config.subscription_id", self._config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str'), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str'), + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + request = self._client.get(url, query_parameters, header_parameters) + pipeline_response = self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.GraphQueryError, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('GraphQueryResource', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + get.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ResourceGraph/queries/{resourceName}'} # type: ignore + + def delete( + self, + resource_group_name, # type: str + resource_name, # type: str + **kwargs # type: Any + ): + # type: (...) -> None + """Delete a graph query. + + :param resource_group_name: The name of the resource group. + :type resource_group_name: str + :param resource_name: The name of the Graph Query resource. + :type resource_name: str + :keyword callable cls: A custom type or function that will be passed the direct response + :return: None, or the result of cls(response) + :rtype: None + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType[None] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2018-09-01-preview" + accept = "application/json" + + # Construct URL + url = self.delete.metadata['url'] # type: ignore + path_format_arguments = { + 'subscriptionId': self._serialize.url("self._config.subscription_id", self._config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str'), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str'), + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + request = self._client.delete(url, query_parameters, header_parameters) + pipeline_response = self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200, 204]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.GraphQueryError, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + if cls: + return cls(pipeline_response, None, {}) + + delete.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ResourceGraph/queries/{resourceName}'} # type: ignore + + def create_or_update( + self, + resource_group_name, # type: str + resource_name, # type: str + properties, # type: "_models.GraphQueryResource" + **kwargs # type: Any + ): + # type: (...) -> "_models.GraphQueryResource" + """Create a new graph query. + + :param resource_group_name: The name of the resource group. + :type resource_group_name: str + :param resource_name: The name of the Graph Query resource. + :type resource_name: str + :param properties: Properties that need to be specified to create a new graph query. + :type properties: ~azure.mgmt.resourcegraph.models.GraphQueryResource + :keyword callable cls: A custom type or function that will be passed the direct response + :return: GraphQueryResource, or the result of cls(response) + :rtype: ~azure.mgmt.resourcegraph.models.GraphQueryResource + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.GraphQueryResource"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2018-09-01-preview" + content_type = kwargs.pop("content_type", "application/json") + accept = "application/json" + + # Construct URL + url = self.create_or_update.metadata['url'] # type: ignore + path_format_arguments = { + 'subscriptionId': self._serialize.url("self._config.subscription_id", self._config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str'), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str'), + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = self._serialize.header("content_type", content_type, 'str') + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + body_content = self._serialize.body(properties, 'GraphQueryResource') + body_content_kwargs['content'] = body_content + request = self._client.put(url, query_parameters, header_parameters, **body_content_kwargs) + pipeline_response = self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.GraphQueryError, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('GraphQueryResource', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + create_or_update.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ResourceGraph/queries/{resourceName}'} # type: ignore + + def update( + self, + resource_group_name, # type: str + resource_name, # type: str + body, # type: "_models.GraphQueryUpdateParameters" + **kwargs # type: Any + ): + # type: (...) -> "_models.GraphQueryResource" + """Updates a graph query that has already been added. + + :param resource_group_name: The name of the resource group. + :type resource_group_name: str + :param resource_name: The name of the Graph Query resource. + :type resource_name: str + :param body: Properties that need to be specified to create a new graph query. + :type body: ~azure.mgmt.resourcegraph.models.GraphQueryUpdateParameters + :keyword callable cls: A custom type or function that will be passed the direct response + :return: GraphQueryResource, or the result of cls(response) + :rtype: ~azure.mgmt.resourcegraph.models.GraphQueryResource + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.GraphQueryResource"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2018-09-01-preview" + content_type = kwargs.pop("content_type", "application/json") + accept = "application/json" + + # Construct URL + url = self.update.metadata['url'] # type: ignore + path_format_arguments = { + 'subscriptionId': self._serialize.url("self._config.subscription_id", self._config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str'), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str'), + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = self._serialize.header("content_type", content_type, 'str') + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + body_content = self._serialize.body(body, 'GraphQueryUpdateParameters') + body_content_kwargs['content'] = body_content + request = self._client.patch(url, query_parameters, header_parameters, **body_content_kwargs) + pipeline_response = self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.GraphQueryError, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('GraphQueryResource', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + update.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ResourceGraph/queries/{resourceName}'} # type: ignore diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/operations/_operations.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/operations/_operations.py new file mode 100644 index 00000000000..baeed524e44 --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/operations/_operations.py @@ -0,0 +1,110 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +from typing import TYPE_CHECKING +import warnings + +from azure.core.exceptions import ClientAuthenticationError, HttpResponseError, ResourceExistsError, ResourceNotFoundError, map_error +from azure.core.paging import ItemPaged +from azure.core.pipeline import PipelineResponse +from azure.core.pipeline.transport import HttpRequest, HttpResponse +from azure.mgmt.core.exceptions import ARMErrorFormat + +from .. import models as _models + +if TYPE_CHECKING: + # pylint: disable=unused-import,ungrouped-imports + from typing import Any, Callable, Dict, Generic, Iterable, Optional, TypeVar + + T = TypeVar('T') + ClsType = Optional[Callable[[PipelineResponse[HttpRequest, HttpResponse], T, Dict[str, Any]], Any]] + +class Operations(object): + """Operations operations. + + You should not instantiate this class directly. Instead, you should create a Client instance that + instantiates it for you and attaches it as an attribute. + + :ivar models: Alias to model classes used in this operation group. + :type models: ~azure.mgmt.resourcegraph.models + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + """ + + models = _models + + def __init__(self, client, config, serializer, deserializer): + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self._config = config + + def list( + self, + **kwargs # type: Any + ): + # type: (...) -> Iterable["_models.OperationListResult"] + """Lists all of the available REST API operations. + + :keyword callable cls: A custom type or function that will be passed the direct response + :return: An iterator like instance of either OperationListResult or the result of cls(response) + :rtype: ~azure.core.paging.ItemPaged[~azure.mgmt.resourcegraph.models.OperationListResult] + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.OperationListResult"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2021-03-01" + accept = "application/json" + + def prepare_request(next_link=None): + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + if not next_link: + # Construct URL + url = self.list.metadata['url'] # type: ignore + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + request = self._client.get(url, query_parameters, header_parameters) + else: + url = next_link + query_parameters = {} # type: Dict[str, Any] + request = self._client.get(url, query_parameters, header_parameters) + return request + + def extract_data(pipeline_response): + deserialized = self._deserialize('OperationListResult', pipeline_response) + list_of_elem = deserialized.value + if cls: + list_of_elem = cls(list_of_elem) + return None, iter(list_of_elem) + + def get_next(next_link=None): + request = prepare_request(next_link) + + pipeline_response = self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + error = self._deserialize(_models.ErrorResponse, response) + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + return pipeline_response + + return ItemPaged( + get_next, extract_data + ) + list.metadata = {'url': '/providers/Microsoft.ResourceGraph/operations'} # type: ignore diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/operations/_resource_graph_client_operations.py b/src/zones/azext_zones/vendored_sdks/resourcegraph/operations/_resource_graph_client_operations.py new file mode 100644 index 00000000000..71ada398b4d --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/operations/_resource_graph_client_operations.py @@ -0,0 +1,249 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +from typing import TYPE_CHECKING +import warnings + +from azure.core.exceptions import ClientAuthenticationError, HttpResponseError, ResourceExistsError, ResourceNotFoundError, map_error +from azure.core.pipeline import PipelineResponse +from azure.core.pipeline.transport import HttpRequest, HttpResponse +from azure.mgmt.core.exceptions import ARMErrorFormat + +from .. import models as _models + +if TYPE_CHECKING: + # pylint: disable=unused-import,ungrouped-imports + from typing import Any, Callable, Dict, Generic, List, Optional, TypeVar + + T = TypeVar('T') + ClsType = Optional[Callable[[PipelineResponse[HttpRequest, HttpResponse], T, Dict[str, Any]], Any]] + +class ResourceGraphClientOperationsMixin(object): + + def resource_changes( + self, + parameters, # type: "_models.ResourceChangesRequestParameters" + **kwargs # type: Any + ): + # type: (...) -> "_models.ResourceChangeList" + """List changes to a resource for a given time interval. + + :param parameters: the parameters for this request for changes. + :type parameters: ~azure.mgmt.resourcegraph.models.ResourceChangesRequestParameters + :keyword callable cls: A custom type or function that will be passed the direct response + :return: ResourceChangeList, or the result of cls(response) + :rtype: ~azure.mgmt.resourcegraph.models.ResourceChangeList + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.ResourceChangeList"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2020-09-01-preview" + content_type = kwargs.pop("content_type", "application/json") + accept = "application/json" + + # Construct URL + url = self.resource_changes.metadata['url'] # type: ignore + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = self._serialize.header("content_type", content_type, 'str') + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + body_content = self._serialize.body(parameters, 'ResourceChangesRequestParameters') + body_content_kwargs['content'] = body_content + request = self._client.post(url, query_parameters, header_parameters, **body_content_kwargs) + pipeline_response = self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.ErrorResponse, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('ResourceChangeList', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + resource_changes.metadata = {'url': '/providers/Microsoft.ResourceGraph/resourceChanges'} # type: ignore + + def resource_change_details( + self, + parameters, # type: "_models.ResourceChangeDetailsRequestParameters" + **kwargs # type: Any + ): + # type: (...) -> List["_models.ResourceChangeData"] + """Get resource change details. + + :param parameters: The parameters for this request for resource change details. + :type parameters: ~azure.mgmt.resourcegraph.models.ResourceChangeDetailsRequestParameters + :keyword callable cls: A custom type or function that will be passed the direct response + :return: list of ResourceChangeData, or the result of cls(response) + :rtype: list[~azure.mgmt.resourcegraph.models.ResourceChangeData] + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType[List["_models.ResourceChangeData"]] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2020-09-01-preview" + content_type = kwargs.pop("content_type", "application/json") + accept = "application/json" + + # Construct URL + url = self.resource_change_details.metadata['url'] # type: ignore + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = self._serialize.header("content_type", content_type, 'str') + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + body_content = self._serialize.body(parameters, 'ResourceChangeDetailsRequestParameters') + body_content_kwargs['content'] = body_content + request = self._client.post(url, query_parameters, header_parameters, **body_content_kwargs) + pipeline_response = self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.ErrorResponse, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('[ResourceChangeData]', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + resource_change_details.metadata = {'url': '/providers/Microsoft.ResourceGraph/resourceChangeDetails'} # type: ignore + + def resources( + self, + query, # type: "_models.QueryRequest" + **kwargs # type: Any + ): + # type: (...) -> "_models.QueryResponse" + """Queries the resources managed by Azure Resource Manager for scopes specified in the request. + + :param query: Request specifying query and its options. + :type query: ~azure.mgmt.resourcegraph.models.QueryRequest + :keyword callable cls: A custom type or function that will be passed the direct response + :return: QueryResponse, or the result of cls(response) + :rtype: ~azure.mgmt.resourcegraph.models.QueryResponse + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType["_models.QueryResponse"] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2021-03-01" + content_type = kwargs.pop("content_type", "application/json") + accept = "application/json" + + # Construct URL + url = self.resources.metadata['url'] # type: ignore + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = self._serialize.header("content_type", content_type, 'str') + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + body_content = self._serialize.body(query, 'QueryRequest') + body_content_kwargs['content'] = body_content + request = self._client.post(url, query_parameters, header_parameters, **body_content_kwargs) + pipeline_response = self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.ErrorResponse, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('QueryResponse', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + resources.metadata = {'url': '/providers/Microsoft.ResourceGraph/resources'} # type: ignore + + def resources_history( + self, + request, # type: "_models.ResourcesHistoryRequest" + **kwargs # type: Any + ): + # type: (...) -> object + """List all snapshots of a resource for a given time interval. + + :param request: + :type request: ~azure.mgmt.resourcegraph.models.ResourcesHistoryRequest + :keyword callable cls: A custom type or function that will be passed the direct response + :return: object, or the result of cls(response) + :rtype: object + :raises: ~azure.core.exceptions.HttpResponseError + """ + cls = kwargs.pop('cls', None) # type: ClsType[object] + error_map = { + 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError + } + error_map.update(kwargs.pop('error_map', {})) + api_version = "2020-04-01-preview" + content_type = kwargs.pop("content_type", "application/json") + accept = "application/json" + + # Construct URL + url = self.resources_history.metadata['url'] # type: ignore + + # Construct parameters + query_parameters = {} # type: Dict[str, Any] + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} # type: Dict[str, Any] + header_parameters['Content-Type'] = self._serialize.header("content_type", content_type, 'str') + header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') + + body_content_kwargs = {} # type: Dict[str, Any] + body_content = self._serialize.body(request, 'ResourcesHistoryRequest') + body_content_kwargs['content'] = body_content + request = self._client.post(url, query_parameters, header_parameters, **body_content_kwargs) + pipeline_response = self._client._pipeline.run(request, stream=False, **kwargs) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize(_models.ErrorResponse, response) + raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat) + + deserialized = self._deserialize('object', pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, {}) + + return deserialized + resources_history.metadata = {'url': '/providers/Microsoft.ResourceGraph/resourcesHistory'} # type: ignore diff --git a/src/zones/azext_zones/vendored_sdks/resourcegraph/py.typed b/src/zones/azext_zones/vendored_sdks/resourcegraph/py.typed new file mode 100644 index 00000000000..e5aff4f83af --- /dev/null +++ b/src/zones/azext_zones/vendored_sdks/resourcegraph/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. \ No newline at end of file diff --git a/src/zones/setup.cfg b/src/zones/setup.cfg new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/zones/setup.py b/src/zones/setup.py new file mode 100644 index 00000000000..9b0964a0c49 --- /dev/null +++ b/src/zones/setup.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +from codecs import open +from setuptools import setup, find_packages +try: + from azure_bdist_wheel import cmdclass +except ImportError: + from distutils import log as logger + logger.warn("Wheel is not available, disabling bdist_wheel hook") + +# TODO: Confirm this is the right version number you want and it matches your +# HISTORY.rst entry. +VERSION = '0.1.0' + +# The full list of classifiers is available at +# https://pypi.python.org/pypi?%3Aaction=list_classifiers +CLASSIFIERS = [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: System Administrators', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'License :: OSI Approved :: MIT License', +] + +# TODO: Add any additional SDK dependencies here +DEPENDENCIES = [] + +with open('README.rst', 'r', encoding='utf-8') as f: + README = f.read() +with open('HISTORY.rst', 'r', encoding='utf-8') as f: + HISTORY = f.read() + +setup( + name='zones', + version=VERSION, + description='Microsoft Azure Command-Line Tools Zones Extension', + # TODO: Update author and email, if applicable + author='Microsoft Corporation', + author_email='azpycli@microsoft.com', + # TODO: change to your extension source code repo if the code will not be put in azure-cli-extensions repo + url='https://github.com/Azure/azure-cli-extensions/tree/master/src/zones', + long_description=README + '\n\n' + HISTORY, + license='MIT', + classifiers=CLASSIFIERS, + packages=find_packages(), + install_requires=DEPENDENCIES, + package_data={'azext_zones': ['azext_metadata.json']}, +) \ No newline at end of file From 8826e4da557a3769d2874cd6f0f6fed6bfbbd524 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Wed, 23 Apr 2025 17:48:19 +0200 Subject: [PATCH 02/40] add table output --- src/zones/azext_zones/custom.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/zones/azext_zones/custom.py b/src/zones/azext_zones/custom.py index 8144c4b2d55..d76a09c56f6 100644 --- a/src/zones/azext_zones/custom.py +++ b/src/zones/azext_zones/custom.py @@ -10,7 +10,7 @@ __logger = get_logger(__name__) -def validate_zones(client, resource_group_names): +def validate_zones(client, cmd, resource_group_names): # Build the ARG query to retrieve resources query = build_arg_query(resource_group_names, None, None) __logger.warning("Query: %s", query) @@ -24,6 +24,22 @@ def validate_zones(client, resource_group_names): # Present the results to the user return validation_results + if(cmd.output == 'table'): + # Output results in table format + from knack.table import TableFormatter + from knack.output import table_output + + table = TableFormatter(cmd, cmd.output).get_table() + table.add_column('Name', 'name') + table.add_column('Location', 'location') + table.add_column('Resource Group', 'resourceGroup') + table.add_column('Type', 'type') + table.add_column('Zone Redundant', 'zoneRedundant') + table.add_row(validation_results) + table_output(cmd, table, validation_results) + + return validation_results + def validate_resources(resources): resource_results = [] From 21b85eeed1fd8cfab164c33f27ae340372af715f Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 24 Apr 2025 12:33:20 +0200 Subject: [PATCH 03/40] added location data lookup --- src/zones/README.rst | 20 ++++++++- src/zones/azext_zones/_argHelper.py | 9 ++-- src/zones/azext_zones/_clients.py | 25 ++++------- src/zones/azext_zones/_help.py | 4 +- src/zones/azext_zones/_locationHelper.py | 42 +++++++++++++++++++ src/zones/azext_zones/_params.py | 1 - ...lidation.py => _resourceTypeValidation.py} | 2 + src/zones/azext_zones/azext_metadata.json | 2 +- src/zones/azext_zones/commands.py | 5 +-- src/zones/azext_zones/custom.py | 36 ++++++++++------ .../microsoft_storage.py | 18 ++++---- src/zones/setup.py | 8 ++-- 12 files changed, 113 insertions(+), 59 deletions(-) create mode 100644 src/zones/azext_zones/_locationHelper.py rename src/zones/azext_zones/{resourceTypeValidation.py => _resourceTypeValidation.py} (90%) diff --git a/src/zones/README.rst b/src/zones/README.rst index dcb245c467b..6b811d34c98 100644 --- a/src/zones/README.rst +++ b/src/zones/README.rst @@ -2,4 +2,22 @@ Microsoft Azure CLI 'zones' Extension ========================================== This package is for the 'zones' extension. -i.e. 'az zones' \ No newline at end of file +i.e. 'az zones' + +This CLI Extension helps validate the zone redundancy status of resources within a specific scope. +For each resource, one of the following statuses will be returned: + Unknown # Unable to verify status. You'll need to check the resource manually. + Yes # Resource is configured for zone redundancy + Always # Resource is always zone redundant, no configuration needed + No # Resource is not configured for zone redundancy, but could be in another configuration + Never # Resource cannot be configured for zone redundancy + Dependent # Resource is zone redundant if parent or related resource is zone redundant + NoZonesInRegion # The region the resource is deployed in does not have Availability Zones + +USAGE + +Validate all resources in current scope to which you have read access: +az zones validate + +Validate all resources in specific resource groups to which you have read access: +az zones validate [--resource-groups , ...] diff --git a/src/zones/azext_zones/_argHelper.py b/src/zones/azext_zones/_argHelper.py index 657d08eb753..3615be48735 100644 --- a/src/zones/azext_zones/_argHelper.py +++ b/src/zones/azext_zones/_argHelper.py @@ -4,7 +4,7 @@ from knack.log import get_logger from .vendored_sdks.resourcegraph.models import ResultTruncated -from .vendored_sdks.resourcegraph.models import QueryRequest, QueryRequestOptions, QueryResponse, ResultFormat, Error +from .vendored_sdks.resourcegraph.models import QueryRequest, QueryRequestOptions, QueryResponse, ResultFormat from azure.cli.core._profile import Profile from azure.core.exceptions import HttpResponseError @@ -16,16 +16,13 @@ __logger = get_logger(__name__) -def build_arg_query(resource_groups, tags, attributes): - # type: (list[str], list[str], list[str]) -> str +def build_arg_query(resource_groups, attributes): + # type: (list[str], list[str]) -> str query = "Resources" if resource_groups is not None and len(resource_groups) > 0: query += " | where resourceGroup in ({0})".format(','.join(f"'{item}'" for item in resource_groups.split(','))) - # if tags is not None and len(tags) > 0: - # query += " | where tags['{0}'] =~ '{1}'".format(tags[0], tags[1]) - if attributes is not None and len(attributes) > 0: query += " | project {0}".format(', '.join(attributes)) diff --git a/src/zones/azext_zones/_clients.py b/src/zones/azext_zones/_clients.py index 875ec5252ee..dd27bcfb490 100644 --- a/src/zones/azext_zones/_clients.py +++ b/src/zones/azext_zones/_clients.py @@ -1,29 +1,18 @@ from azure.cli.core.util import send_raw_request from azure.cli.core.commands.client_factory import get_subscription_id - -API_VERSION = "2022-10-01" - - -class ARGClient(): +class MgmtApiClient(): @classmethod - def query(cls, cmd, query): - management_hostname = cmd.cli_ctx.cloud.endpoints.management - api_version = API_VERSION + def query(cls, cmd, method, resource, api_version, requestBody): + management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager sub_id = get_subscription_id(cmd.cli_ctx) - url_fmt = ("{}/providers/Microsoft.ResourceGraph/resources?api-version={}") + url_fmt = ("{}/subscriptions/{}/{}?api-version={}") request_url = url_fmt.format( management_hostname.strip('/'), + sub_id, + resource, api_version) - requestBody = { - "subscriptions": [sub_id], - "query": query, - "options": { - "resultFormat": "objectArray" - } - } - - r = send_raw_request(cmd.cli_ctx, "POST", request_url, body=requestBody) + r = send_raw_request(cmd.cli_ctx, method, request_url, body=requestBody) return r.json() diff --git a/src/zones/azext_zones/_help.py b/src/zones/azext_zones/_help.py index 93c84e12844..e9f354f5043 100644 --- a/src/zones/azext_zones/_help.py +++ b/src/zones/azext_zones/_help.py @@ -9,10 +9,10 @@ helps['zones'] = """ type: group - short-summary: Commands to manage Zoness. + short-summary: Commands to validate Availability Zone Configuration. Use one of the options below. """ helps['zones validate'] = """ type: command - short-summary: Validates zone redundancy status. + short-summary: Validates zone redundancy status of all resources in the current subscription context for which you have read access. """ diff --git a/src/zones/azext_zones/_locationHelper.py b/src/zones/azext_zones/_locationHelper.py new file mode 100644 index 00000000000..910d18fa6fb --- /dev/null +++ b/src/zones/azext_zones/_locationHelper.py @@ -0,0 +1,42 @@ +from ._clients import MgmtApiClient +from knack.log import get_logger + +class LocationDataHelper: + + _location_data = None + _logger = None + + def __init__(self, cmd): + self.cmd = cmd + self._logger = get_logger(__name__) + + def get_location_data(self): + if not LocationDataHelper._location_data: + # query(cls, cmd, method, resource, api-version, requestBody): + try: + LocationDataHelper._location_data = MgmtApiClient.query( + self.cmd, + "GET", + "locations", + "2022-12-01", + None + ) + except Exception as e: + self._logger.warning(f"An error occurred while querying location data: {e}. No location data will be used. Please validate manually if the regions used support Availability Zones.") + + self._logger.debug(f"Loaded location data successfully.") + + def region_has_zones(region): + if not LocationDataHelper._location_data: + return None + + # While 'global' is not a valid region, we want to return true for global resources + if region == 'global': + return True + + if LocationDataHelper._location_data: + for location in LocationDataHelper._location_data['value']: + if location['name'].lower() == region.lower(): + return 'availabilityZoneMappings' in location + + return None \ No newline at end of file diff --git a/src/zones/azext_zones/_params.py b/src/zones/azext_zones/_params.py index d1ff3d2fc3b..383ba77511d 100644 --- a/src/zones/azext_zones/_params.py +++ b/src/zones/azext_zones/_params.py @@ -8,4 +8,3 @@ def load_arguments(self, _): with self.argument_context('zones validate') as c: c.argument('resource_group_names', options_list=['--resource-groups', '-g'], help='Name of the resource groups, comma separated.', required=False) - c.argument('tags', options_list=['--tags'], help='Space-separated tags in "key[=value]" format.', required=False) diff --git a/src/zones/azext_zones/resourceTypeValidation.py b/src/zones/azext_zones/_resourceTypeValidation.py similarity index 90% rename from src/zones/azext_zones/resourceTypeValidation.py rename to src/zones/azext_zones/_resourceTypeValidation.py index 31a5de10182..74a622689b9 100644 --- a/src/zones/azext_zones/resourceTypeValidation.py +++ b/src/zones/azext_zones/_resourceTypeValidation.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod from enum import Enum +from ._locationHelper import LocationDataHelper resource_type_validators = {} @@ -36,6 +37,7 @@ class ZoneRedundancyValidationResult(Enum): No = 4 # Resource is not configured for zone redundancy Never = 5 # Resource cannot be configured for zone redundancy Dependent = 6 # Resource is zone redundant if parent or related resource is zone redundant + NoZonesInRegion = 7 # Resource is not zone redundant because the region does not support zones def to_string(value): diff --git a/src/zones/azext_zones/azext_metadata.json b/src/zones/azext_zones/azext_metadata.json index 55c81bf3328..0a142586d70 100644 --- a/src/zones/azext_zones/azext_metadata.json +++ b/src/zones/azext_zones/azext_metadata.json @@ -1,4 +1,4 @@ { - "azext.isPreview": true, + "azext.isExperimental": true, "azext.minCliCoreVersion": "2.0.67" } \ No newline at end of file diff --git a/src/zones/azext_zones/commands.py b/src/zones/azext_zones/commands.py index c8182381931..69676e84de3 100644 --- a/src/zones/azext_zones/commands.py +++ b/src/zones/azext_zones/commands.py @@ -9,8 +9,5 @@ def load_command_table(self, _): - with self.command_group('zones', client_factory=cf_zones) as g: + with self.command_group('zones', client_factory=cf_zones, is_experimental=True) as g: g.custom_command('validate', 'validate_zones') - - with self.command_group('zones', is_preview=True): - pass diff --git a/src/zones/azext_zones/custom.py b/src/zones/azext_zones/custom.py index d76a09c56f6..286fc1b869f 100644 --- a/src/zones/azext_zones/custom.py +++ b/src/zones/azext_zones/custom.py @@ -4,19 +4,25 @@ # -------------------------------------------------------------------------------------------- from knack.log import get_logger -from .resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult from ._argHelper import build_arg_query, execute_arg_query +from ._locationHelper import LocationDataHelper __logger = get_logger(__name__) def validate_zones(client, cmd, resource_group_names): + + # Get the location data we'll use to validate the resources + location_data_helper = LocationDataHelper(cmd) + location_data = location_data_helper.get_location_data() + # Build the ARG query to retrieve resources - query = build_arg_query(resource_group_names, None, None) - __logger.warning("Query: %s", query) + query = build_arg_query(resource_group_names, None) + __logger.debug("Built ARG Query: %s", query) # Retrieve the list of resources to validate - resources = execute_arg_query(client, query, 100, 0, None, None, False, None) + resources = execute_arg_query(client, query, None, 0, None, None, False, None) # Run validation on the retrieved resources validation_results = validate_resources(resources) @@ -30,14 +36,9 @@ def validate_zones(client, cmd, resource_group_names): from knack.output import table_output table = TableFormatter(cmd, cmd.output).get_table() - table.add_column('Name', 'name') - table.add_column('Location', 'location') - table.add_column('Resource Group', 'resourceGroup') - table.add_column('Type', 'type') - table.add_column('Zone Redundant', 'zoneRedundant') - table.add_row(validation_results) table_output(cmd, table, validation_results) + # Default to json output if no specific format is requested return validation_results @@ -50,13 +51,22 @@ def validate_resources(resources): # Loop through the resources and validate each one for resource in resources['data']: resourceProvider = resource['type'].split('/')[0] - validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) - zrStatus = ZoneRedundancyValidationResult.Unknown if validator is None else validator.validate(resource) + region = resource['location'] + zrStatus = None + + # If the region does not have zones, we need to look no further + regionHasZones = LocationDataHelper.region_has_zones(region) + if(regionHasZones is False): + zrStatus = ZoneRedundancyValidationResult.NoZonesInRegion + else: + validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + zrStatus = ZoneRedundancyValidationResult.Unknown if validator is None else validator.validate(resource) + resource_result = {} resource_result['name'] = resource['name'] resource_result['location'] = resource['location'] resource_result['resourceGroup'] = resource['resourceGroup'] - resource_result['type'] = resource['type'] + resource_result['resourceType'] = resource['type'] resource_result['zoneRedundant'] = ZoneRedundancyValidationResult.to_string(zrStatus) resource_results.append(resource_result) diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py index 1af0b6cfca2..0bc4bf7963e 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py @@ -1,18 +1,20 @@ -from ..resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type -# from knack.log import get_logger -# __logger = get_logger(__name__) +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + @register_resource_type('microsoft.storage') -class microsoft_storage_storageAccount: +class microsoft_storage: @staticmethod def validate(resource): resourceSubType = resource['type'].split('/')[1] - resourceTypes = { - 'storageaccounts': + _logger = get_logger("microsoft_storage") + _logger.debug("Validating Microsoft.Storage resource type: %s", resourceSubType) + + match resourceSubType: + case 'storageaccounts': # Storage accounts are zone redundant if they are in the ZRS SKU - ZoneRedundancyValidationResult.Yes if resource['sku']['name'] == 'Standard_ZRS' else ZoneRedundancyValidationResult.No - } + return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] == 'Standard_ZRS' else ZoneRedundancyValidationResult.No return resourceTypes.get(resourceSubType, ZoneRedundancyValidationResult.Unknown) \ No newline at end of file diff --git a/src/zones/setup.py b/src/zones/setup.py index 9b0964a0c49..90fd0c5c902 100644 --- a/src/zones/setup.py +++ b/src/zones/setup.py @@ -42,12 +42,10 @@ setup( name='zones', - version=VERSION, + version=0.1, description='Microsoft Azure Command-Line Tools Zones Extension', - # TODO: Update author and email, if applicable - author='Microsoft Corporation', - author_email='azpycli@microsoft.com', - # TODO: change to your extension source code repo if the code will not be put in azure-cli-extensions repo + author='Niels Buit', + author_email='nielsb@microsoft.com', url='https://github.com/Azure/azure-cli-extensions/tree/master/src/zones', long_description=README + '\n\n' + HISTORY, license='MIT', From 526cc47da6b0a132d919ee088a1a867be5751ebb Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 24 Apr 2025 12:33:36 +0200 Subject: [PATCH 04/40] added network provider --- .../microsoft_network.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_network.py diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_network.py b/src/zones/azext_zones/resource_type_validators/microsoft_network.py new file mode 100644 index 00000000000..df7921e94c1 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_network.py @@ -0,0 +1,73 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.network') +class microsoft_network: + + @staticmethod + def validate(resource): + resourceSubType = resource['type'].split('/')[1] + + _logger = get_logger("microsoft_network") + _logger.debug("Validating Microsoft.Network resource type: %s", resourceSubType) + + match resourceSubType: + case 'applicationgateways': + return ZoneRedundancyValidationResult.Yes if resource['zones'] not in [None, []] else ZoneRedundancyValidationResult.No + + case 'azurefirewalls': + return ZoneRedundancyValidationResult.Yes if resource['zones'] not in [None, []] and resource['sku']['capacity'] > 1 else ZoneRedundancyValidationResult.No + + case 'connections': + # Network connections depend on the configuration of the Virtual Network Gateway + return ZoneRedundancyValidationResult.Dependent + + case 'dnszones': + # Azure DNS is a global service, zone redundant by default + return ZoneRedundancyValidationResult.Always + + case 'frontdoors': + # Front Door is a global resources and always zone redundant + return ZoneRedundancyValidationResult.Always + + case 'loadbalancers': + return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] in ['Standard'] and resource['frontendipconfigurations'][0].zones not in [None, []] else ZoneRedundancyValidationResult.No + + case 'localnetworkgateways': + # Local network gateways depend on the configuration of the VPN Gateway + return ZoneRedundancyValidationResult.Dependent + + case 'networkinterfaces': + # Network interfaces are in the zone of the virtual machines they are attached to + return ZoneRedundancyValidationResult.Dependent + + case 'networksecuritygroups': + return ZoneRedundancyValidationResult.Always + + case 'networkwatchers': + # Network watchers are zone redundant by default + return ZoneRedundancyValidationResult.Always + + case 'privatednszones': + # Private DNS zones are zone redundant by default + # https://learn.microsoft.com/azure/dns/private-dns-resiliency + return ZoneRedundancyValidationResult.Always + + case 'privateendpoints': + return ZoneRedundancyValidationResult.Always + + case 'publicipaddresses': + return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] in ['Standard'] and resource['zones'] not in [None, []] else ZoneRedundancyValidationResult.No + + case 'virtualnetworks': + # Virtual networks span all availability zones in a region. + # https://learn.microsoft.com/azure/virtual-network/virtual-networks-overview#virtual-networks-and-availability-zones + return ZoneRedundancyValidationResult.Always + + case 'virtualnetworkgateways': + sku = resource['properties']['sku']['name'] + return ZoneRedundancyValidationResult.Yes if sku.endswith('AZ') else ZoneRedundancyValidationResult.No + + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file From c6039ab027fbf74cf0f7d5883e4a697d91d41dcb Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 24 Apr 2025 13:09:22 +0200 Subject: [PATCH 05/40] fix output formatting --- src/zones/azext_zones/_argHelper.py | 1 - src/zones/azext_zones/custom.py | 12 ------------ 2 files changed, 13 deletions(-) diff --git a/src/zones/azext_zones/_argHelper.py b/src/zones/azext_zones/_argHelper.py index 3615be48735..6f4567d9407 100644 --- a/src/zones/azext_zones/_argHelper.py +++ b/src/zones/azext_zones/_argHelper.py @@ -5,7 +5,6 @@ from .vendored_sdks.resourcegraph.models import ResultTruncated from .vendored_sdks.resourcegraph.models import QueryRequest, QueryRequestOptions, QueryResponse, ResultFormat - from azure.cli.core._profile import Profile from azure.core.exceptions import HttpResponseError from azure.cli.core.azclierror import BadRequestError, AzureInternalError diff --git a/src/zones/azext_zones/custom.py b/src/zones/azext_zones/custom.py index 286fc1b869f..b7206419d5b 100644 --- a/src/zones/azext_zones/custom.py +++ b/src/zones/azext_zones/custom.py @@ -27,18 +27,6 @@ def validate_zones(client, cmd, resource_group_names): # Run validation on the retrieved resources validation_results = validate_resources(resources) - # Present the results to the user - return validation_results - - if(cmd.output == 'table'): - # Output results in table format - from knack.table import TableFormatter - from knack.output import table_output - - table = TableFormatter(cmd, cmd.output).get_table() - table_output(cmd, table, validation_results) - - # Default to json output if no specific format is requested return validation_results From 15eb080c5063951d3bfc6e64c00f9ec2cd5b8566 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 24 Apr 2025 13:41:12 +0200 Subject: [PATCH 06/40] add basic compute types --- .../microsoft_compute.py | 32 +++++++++++++++++++ .../microsoft_storage.py | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_compute.py diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py new file mode 100644 index 00000000000..4412ffd6aaf --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py @@ -0,0 +1,32 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.compute') +class microsoft_compute: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_compute") + _logger.debug("Validating Microsoft.Compute resource type: %s", resourceSubType) + + match resourceSubType: + case 'disks': + return ZoneRedundancyValidationResult.Yes if resource['zones'] is not None and len(resource['zones']) > 1 else ZoneRedundancyValidationResult.No + + case 'virtualmachinescalesets': + # VMSS is ZR if deployed to more than one zone + return ZoneRedundancyValidationResult.Yes if resource['zones'] is not None and len(resource['zones']) > 1 else ZoneRedundancyValidationResult.No + + case 'virtualmachines': + # VM is ZR if deployed to more than one zone + return ZoneRedundancyValidationResult.Yes if resource['zones'] is not None and len(resource['zones']) > 1 else ZoneRedundancyValidationResult.No + + case 'virtualmachines/extensions': + # VM extensions are zone redundant if the VM they are attached to is zone redundant + return ZoneRedundancyValidationResult.Dependent + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py index 0bc4bf7963e..6d3cf2b167f 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py @@ -17,4 +17,4 @@ def validate(resource): # Storage accounts are zone redundant if they are in the ZRS SKU return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] == 'Standard_ZRS' else ZoneRedundancyValidationResult.No - return resourceTypes.get(resourceSubType, ZoneRedundancyValidationResult.Unknown) \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file From 13fb891aeb7aa1ca72bb208097cab4f449559c66 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 24 Apr 2025 15:31:20 +0200 Subject: [PATCH 07/40] formatting fixes --- src/zones/azext_zones/_argHelper.py | 6 ++++-- src/zones/azext_zones/_clients.py | 3 +-- src/zones/azext_zones/_locationHelper.py | 17 ++++++++++------- .../azext_zones/_resourceTypeValidation.py | 1 - src/zones/azext_zones/custom.py | 6 +++--- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/zones/azext_zones/_argHelper.py b/src/zones/azext_zones/_argHelper.py index 6f4567d9407..569ffa08243 100644 --- a/src/zones/azext_zones/_argHelper.py +++ b/src/zones/azext_zones/_argHelper.py @@ -4,7 +4,7 @@ from knack.log import get_logger from .vendored_sdks.resourcegraph.models import ResultTruncated -from .vendored_sdks.resourcegraph.models import QueryRequest, QueryRequestOptions, QueryResponse, ResultFormat +from .vendored_sdks.resourcegraph.models import QueryRequest, QueryRequestOptions, QueryResponse, ResultFormat, Error from azure.cli.core._profile import Profile from azure.core.exceptions import HttpResponseError from azure.cli.core.azclierror import BadRequestError, AzureInternalError @@ -28,7 +28,9 @@ def build_arg_query(resource_groups, attributes): return query -def execute_arg_query(client, graph_query, first, skip, subscriptions, management_groups, allow_partial_scopes, skip_token): +def execute_arg_query( + client, graph_query, first, skip, subscriptions, management_groups, allow_partial_scopes, skip_token): + mgs_list = management_groups if mgs_list is not None and len(mgs_list) > __MANAGEMENT_GROUP_LIMIT: mgs_list = mgs_list[:__MANAGEMENT_GROUP_LIMIT] diff --git a/src/zones/azext_zones/_clients.py b/src/zones/azext_zones/_clients.py index dd27bcfb490..55759c9551a 100644 --- a/src/zones/azext_zones/_clients.py +++ b/src/zones/azext_zones/_clients.py @@ -3,8 +3,7 @@ class MgmtApiClient(): - @classmethod - def query(cls, cmd, method, resource, api_version, requestBody): + def query(self, cmd, method, resource, api_version, requestBody): management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager sub_id = get_subscription_id(cmd.cli_ctx) url_fmt = ("{}/subscriptions/{}/{}?api-version={}") diff --git a/src/zones/azext_zones/_locationHelper.py b/src/zones/azext_zones/_locationHelper.py index 910d18fa6fb..82db3e7c59b 100644 --- a/src/zones/azext_zones/_locationHelper.py +++ b/src/zones/azext_zones/_locationHelper.py @@ -1,6 +1,7 @@ from ._clients import MgmtApiClient from knack.log import get_logger + class LocationDataHelper: _location_data = None @@ -14,22 +15,24 @@ def get_location_data(self): if not LocationDataHelper._location_data: # query(cls, cmd, method, resource, api-version, requestBody): try: - LocationDataHelper._location_data = MgmtApiClient.query( + LocationDataHelper._location_data = MgmtApiClient.query(self, self.cmd, "GET", "locations", "2022-12-01", None - ) + ) except Exception as e: - self._logger.warning(f"An error occurred while querying location data: {e}. No location data will be used. Please validate manually if the regions used support Availability Zones.") - + self._logger.warning( + f"An error occurred while querying location data: {e}. No location data will be used." + "Please validate manually if the regions used support Availability Zones.") + self._logger.debug(f"Loaded location data successfully.") def region_has_zones(region): if not LocationDataHelper._location_data: return None - + # While 'global' is not a valid region, we want to return true for global resources if region == 'global': return True @@ -38,5 +41,5 @@ def region_has_zones(region): for location in LocationDataHelper._location_data['value']: if location['name'].lower() == region.lower(): return 'availabilityZoneMappings' in location - - return None \ No newline at end of file + + return None diff --git a/src/zones/azext_zones/_resourceTypeValidation.py b/src/zones/azext_zones/_resourceTypeValidation.py index 74a622689b9..62fe4a23465 100644 --- a/src/zones/azext_zones/_resourceTypeValidation.py +++ b/src/zones/azext_zones/_resourceTypeValidation.py @@ -1,7 +1,6 @@ from abc import ABC, abstractmethod from enum import Enum -from ._locationHelper import LocationDataHelper resource_type_validators = {} diff --git a/src/zones/azext_zones/custom.py b/src/zones/azext_zones/custom.py index b7206419d5b..4bb1a6dadc1 100644 --- a/src/zones/azext_zones/custom.py +++ b/src/zones/azext_zones/custom.py @@ -15,7 +15,7 @@ def validate_zones(client, cmd, resource_group_names): # Get the location data we'll use to validate the resources location_data_helper = LocationDataHelper(cmd) - location_data = location_data_helper.get_location_data() + location_data_helper.get_location_data() # Build the ARG query to retrieve resources query = build_arg_query(resource_group_names, None) @@ -39,8 +39,8 @@ def validate_resources(resources): # Loop through the resources and validate each one for resource in resources['data']: resourceProvider = resource['type'].split('/')[0] - region = resource['location'] - zrStatus = None + region = resource['location'] + zrStatus = None # If the region does not have zones, we need to look no further regionHasZones = LocationDataHelper.region_has_zones(region) From 393cc725fcb6dd35d0f93e5e1f8d4b763dba015a Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 24 Apr 2025 16:21:19 +0200 Subject: [PATCH 08/40] add more common resource types --- .../microsoft_apimanagement.py | 29 ++++++++++++++++++ .../resource_type_validators/microsoft_app.py | 26 ++++++++++++++++ .../microsoft_automation.py | 22 ++++++++++++++ .../microsoft_cache.py | 30 +++++++++++++++++++ .../microsoft_keyvault.py | 22 ++++++++++++++ .../microsoft_network.py | 6 +++- .../resource_type_validators/microsoft_sql.py | 26 ++++++++++++++++ .../microsoft_storage.py | 3 +- 8 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_app.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_automation.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_cache.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_sql.py diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py new file mode 100644 index 00000000000..ce890dc2ffb --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py @@ -0,0 +1,29 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.apimanagement') +class microsoft_apimanagement: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_apimanagement") + _logger.debug("Validating Microsoft.apimanagement resource type: %s", resourceSubType) + + match resourceSubType: + case 'gateways': + # ZR state of the gateway is defined on the service level + return ZoneRedundancyValidationResult.Dependent + + case 'service': + # API Management instances are zone redundant if they are premium and have more than one zone + # https://learn.microsoft.com/azure/api-management/high-availability#availability-zones + return ZoneRedundancyValidationResult.Yes if resource['zones'] not in [None, []] \ + and len(resource['zones']) > 1 \ + and resource['sku']['name'] == 'Premium' \ + else ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_app.py b/src/zones/azext_zones/resource_type_validators/microsoft_app.py new file mode 100644 index 00000000000..b6f209790f0 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_app.py @@ -0,0 +1,26 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.app') +class microsoft_app: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_app") + _logger.debug("Validating Microsoft.app resource type: %s", resourceSubType) + + match resourceSubType: + case 'containerapps': + # Container apps are zone redundant if they are hosted on a zone redundant managedEnvironment + return ZoneRedundancyValidationResult.Dependent + + case 'managedenvironments': + # Managed Environments are zone redundant if the zoneRedundant property is set to true + # https://learn.microsoft.com/azure/reliability/reliability-azure-container-apps#availability-zone-support + return ZoneRedundancyValidationResult.Yes if resource['zoneRedundant'] == 'true' else ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_automation.py b/src/zones/azext_zones/resource_type_validators/microsoft_automation.py new file mode 100644 index 00000000000..ea7c49bd589 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_automation.py @@ -0,0 +1,22 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.automation') +class microsoft_automation: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_automation") + _logger.debug("Validating Microsoft.automation resource type: %s", resourceSubType) + + match resourceSubType: + case 'automationaccounts': + # Automation accounts are zone redundant by default + # https://learn.microsoft.com/azure/automation/automation-availability-zones + return ZoneRedundancyValidationResult.Always + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py new file mode 100644 index 00000000000..00991ded2e0 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py @@ -0,0 +1,30 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.cache') +class microsoft_cache: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_cache") + _logger.debug("Validating Microsoft.cache resource type: %s", resourceSubType) + + match resourceSubType: + case 'redis': + # Redis caches are zone redundant if they are premium SKU and have more than one zone set + # https://learn.microsoft.com/azure/azure-cache-for-redis/cache-high-availability#zone-redundancy + return ZoneRedundancyValidationResult.Yes if resource['zones'] is not None \ + and len(resource['zones']) > 1 \ + and resource['sku']['name'] == 'Premium' \ + else ZoneRedundancyValidationResult.No + + case 'redisenterprise': + return ZoneRedundancyValidationResult.Yes if resource['zones'] is not None \ + and len(resource['zones']) > 1 \ + else ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py b/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py new file mode 100644 index 00000000000..863624efe17 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py @@ -0,0 +1,22 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.keyvault') +class microsoft_keyvault: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_keyvault") + _logger.debug("Validating Microsoft.keyvault resource type: %s", resourceSubType) + + match resourceSubType: + case 'vaults': + # Key vaults are zone redundant by default + # https://learn.microsoft.com/azure/key-vault/general/disaster-recovery-guidance#failover-across-regions + return ZoneRedundancyValidationResult.Always + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_network.py b/src/zones/azext_zones/resource_type_validators/microsoft_network.py index df7921e94c1..f9b823c8cb7 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_network.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_network.py @@ -7,7 +7,8 @@ class microsoft_network: @staticmethod def validate(resource): - resourceSubType = resource['type'].split('/')[1] + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] _logger = get_logger("microsoft_network") _logger.debug("Validating Microsoft.Network resource type: %s", resourceSubType) @@ -53,6 +54,9 @@ def validate(resource): # Private DNS zones are zone redundant by default # https://learn.microsoft.com/azure/dns/private-dns-resiliency return ZoneRedundancyValidationResult.Always + + case 'privatednszones/virtualnetworklinks': + return ZoneRedundancyValidationResult.Always case 'privateendpoints': return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py new file mode 100644 index 00000000000..2d4b7fa893f --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py @@ -0,0 +1,26 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.sql') +class microsoft_sql: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_sql") + _logger.debug("Validating Microsoft.sql resource type: %s", resourceSubType) + + match resourceSubType: + case 'servers/databases': + # https://learn.microsoft.com/azure/azure-sql/database/high-availability-sla-local-zone-redundancy#high-availability-through-zone-redundancy + return ZoneRedundancyValidationResult.Yes if resource['properties']['zoneRedundant'] == True else ZoneRedundancyValidationResult.No + + case 'servers': + # Zone Redundancy for SQL is set at the database level, see above + return ZoneRedundancyValidationResult.Dependent + + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py index 6d3cf2b167f..916bbc10ca1 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py @@ -7,7 +7,8 @@ class microsoft_storage: @staticmethod def validate(resource): - resourceSubType = resource['type'].split('/')[1] + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] _logger = get_logger("microsoft_storage") _logger.debug("Validating Microsoft.Storage resource type: %s", resourceSubType) From 30ec6c16ee15bf562c2aa951d1b537d1e1dce6cc Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 24 Apr 2025 17:13:28 +0200 Subject: [PATCH 09/40] allowing to omit dependent resources for clarity --- src/zones/README.rst | 3 +++ src/zones/azext_zones/_params.py | 3 +++ src/zones/azext_zones/custom.py | 21 +++++++++++---------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/zones/README.rst b/src/zones/README.rst index 6b811d34c98..4c34024348b 100644 --- a/src/zones/README.rst +++ b/src/zones/README.rst @@ -21,3 +21,6 @@ az zones validate Validate all resources in specific resource groups to which you have read access: az zones validate [--resource-groups , ...] + +Omit 'dependent' resources from the output: +az zones validate --omit-dependent-resources \ No newline at end of file diff --git a/src/zones/azext_zones/_params.py b/src/zones/azext_zones/_params.py index 383ba77511d..23a9721ccee 100644 --- a/src/zones/azext_zones/_params.py +++ b/src/zones/azext_zones/_params.py @@ -4,7 +4,10 @@ # -------------------------------------------------------------------------------------------- # pylint: disable=line-too-long +from azure.cli.core.commands.parameters import get_three_state_flag + def load_arguments(self, _): with self.argument_context('zones validate') as c: c.argument('resource_group_names', options_list=['--resource-groups', '-g'], help='Name of the resource groups, comma separated.', required=False) + c.argument('omit_dependent_resources', options_list=['--omit-dependent-resources'], help='Omit dependent resources from validation.', arg_type=get_three_state_flag(), required=False) diff --git a/src/zones/azext_zones/custom.py b/src/zones/azext_zones/custom.py index 4bb1a6dadc1..a7c96bb61c1 100644 --- a/src/zones/azext_zones/custom.py +++ b/src/zones/azext_zones/custom.py @@ -11,7 +11,7 @@ __logger = get_logger(__name__) -def validate_zones(client, cmd, resource_group_names): +def validate_zones(client, cmd, omit_dependent_resources, resource_group_names): # Get the location data we'll use to validate the resources location_data_helper = LocationDataHelper(cmd) @@ -25,12 +25,12 @@ def validate_zones(client, cmd, resource_group_names): resources = execute_arg_query(client, query, None, 0, None, None, False, None) # Run validation on the retrieved resources - validation_results = validate_resources(resources) + validation_results = validate_resources(resources, omit_dependent_resources) return validation_results -def validate_resources(resources): +def validate_resources(resources, omit_dependent_resources=False): resource_results = [] if resources['count'] == 0: errMsg = ("No resources found, validation could not be run.") @@ -50,12 +50,13 @@ def validate_resources(resources): validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) zrStatus = ZoneRedundancyValidationResult.Unknown if validator is None else validator.validate(resource) - resource_result = {} - resource_result['name'] = resource['name'] - resource_result['location'] = resource['location'] - resource_result['resourceGroup'] = resource['resourceGroup'] - resource_result['resourceType'] = resource['type'] - resource_result['zoneRedundant'] = ZoneRedundancyValidationResult.to_string(zrStatus) - resource_results.append(resource_result) + if zrStatus is not ZoneRedundancyValidationResult.Dependent or not omit_dependent_resources: + resource_result = {} + resource_result['name'] = resource['name'] + resource_result['location'] = resource['location'] + resource_result['resourceGroup'] = resource['resourceGroup'] + resource_result['resourceType'] = resource['type'] + resource_result['zoneRedundant'] = ZoneRedundancyValidationResult.to_string(zrStatus) + resource_results.append(resource_result) return resource_results From 4c7e672880c4fbef14b14c82ca5dddda26510577 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 24 Apr 2025 17:43:59 +0200 Subject: [PATCH 10/40] add more resource types --- .../resource_type_validators/microsoft_app.py | 2 +- .../resource_type_validators/microsoft_cdn.py | 21 ++++++++++++++++ .../microsoft_chaos.py | 22 ++++++++++++++++ .../microsoft_containerinstance.py | 22 ++++++++++++++++ .../microsoft_containerregistry.py | 22 ++++++++++++++++ .../microsoft_containerservice.py | 25 +++++++++++++++++++ 6 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_cdn.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_chaos.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_app.py b/src/zones/azext_zones/resource_type_validators/microsoft_app.py index b6f209790f0..cf5034e1181 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_app.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_app.py @@ -23,4 +23,4 @@ def validate(resource): # https://learn.microsoft.com/azure/reliability/reliability-azure-container-apps#availability-zone-support return ZoneRedundancyValidationResult.Yes if resource['zoneRedundant'] == 'true' else ZoneRedundancyValidationResult.No - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py b/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py new file mode 100644 index 00000000000..de09b2199cc --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py @@ -0,0 +1,21 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.cdn') +class microsoft_cdn: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_cdn") + _logger.debug("Validating Microsoft.cdn resource type: %s", resourceSubType) + + match resourceSubType: + case 'profiles': + # Cdn profiles are a global service and are zone redundant by default + return ZoneRedundancyValidationResult.Always + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py b/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py new file mode 100644 index 00000000000..48f8aad42b3 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py @@ -0,0 +1,22 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.chaos') +class microsoft_chaos: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_chaos") + _logger.debug("Validating Microsoft.chaos resource type: %s", resourceSubType) + + match resourceSubType: + case 'experiments': + # chaos profiles are always zone redundant + # https://learn.microsoft.com/azure/reliability/reliability-chaos-studio#availability-zone-support + return ZoneRedundancyValidationResult.Always + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py new file mode 100644 index 00000000000..bd0575c91a1 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py @@ -0,0 +1,22 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.containerinstance') +class microsoft_containerinstance: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_containerinstance") + _logger.debug("Validating Microsoft.containerinstance resource type: %s", resourceSubType) + + match resourceSubType: + case 'containerGroups': + # Container groups of container instances are zonal resources, so they are never zone redundant + # https://learn.microsoft.com/azure/reliability/reliability-containers#availability-zone-support + return ZoneRedundancyValidationResult.Never + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py new file mode 100644 index 00000000000..da3fe84bc72 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py @@ -0,0 +1,22 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.containerregistry') +class microsoft_containerregistry: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_containerregistry") + _logger.debug("Validating Microsoft.containerregistry resource type: %s", resourceSubType) + + match resourceSubType: + case 'registries': + # Container registries are zone redundant if the setting enabled + # https://learn.microsoft.com/azure/container-registry/zone-redundancy + return ZoneRedundancyValidationResult.Yes if resource['properties']['zoneRedundancy'] == 'Enabled' else ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py new file mode 100644 index 00000000000..37f68766dac --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py @@ -0,0 +1,25 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.containerservice') +class microsoft_containerservice: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_containerservice") + _logger.debug("Validating Microsoft.containerservice resource type: %s", resourceSubType) + + match resourceSubType: + case 'managedclusters': + # AKS clusters are zone redundant if the node pools are spread across multiple zones + # Zone Redundancy on AKS involves a lot of configuration steps, testing is required beyond this script. + # https://learn.microsoft.com/azure/aks/availability-zones-overview + poolZones = resource['properties']['agentPoolProfiles'][0]['availabilityZones'] + poolZoneCount = len(poolZones) if poolZones else 0 + return ZoneRedundancyValidationResult.Yes if poolZoneCount > 1 == 'Enabled' else ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown From 1bf201e0c67bd28a8033e85f4e0e96ebb5cabb7e Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Fri, 25 Apr 2025 13:24:07 +0200 Subject: [PATCH 11/40] improve readme --- src/zones/README.md | 58 ++++++++++++++++++++++++++++++++++++++++++++ src/zones/README.rst | 26 -------------------- 2 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 src/zones/README.md delete mode 100644 src/zones/README.rst diff --git a/src/zones/README.md b/src/zones/README.md new file mode 100644 index 00000000000..efc6bd2224f --- /dev/null +++ b/src/zones/README.md @@ -0,0 +1,58 @@ +# Microsoft Azure CLI 'zones' Extension + +This package is for the 'zones' extension. +i.e. 'az zones' + +This CLI Extension helps validate the zone redundancy status of resources within a specific scope. +For each resource, one of the following statuses will be returned: + Unknown # Unable to verify status. You'll need to check the resource manually. + Yes # Resource is configured for zone redundancy + Always # Resource is always zone redundant, no configuration needed + No # Resource is not configured for zone redundancy, but could be in another configuration + Never # Resource cannot be configured for zone redundancy + Dependent # Resource is zone redundant if parent or related resource is zone redundant + NoZonesInRegion # The region the resource is deployed in does not have Availability Zones + +## When should you use this? + +In order to build a fully zone redundant application, you need to satisfy three criteria: + +1) Enable zone redundancy on all PaaS resources in the application +2) Ensure zonal resources are spread across all zones. These are the resources that take a 'zones' attribute in their definition. +3) Validate that your application code is able to handle the loss of a zone, e.g. that connections are retried properly when a dependency is unreachable. + +The _zones_ CLI extension can help with the first two steps. By running this against a specific resource group that contains your production resources, you can be sure that you have not overlooked any resources in your quest for zone redundancy. If the results show 'No' on one of your resources, that means that you need to change the configuration to enable ZR. If it shows 'Never', that probably means you need to deploy multiple of those resources to the different zones manually. + +The third step can be validated using Chaos Engineering practices. On Azure, look into Chaos Studio to get started with that. + +## USAGE + +Validate all resources in current scope to which you have read access: + +```bash +az zones validate +``` + +Get the results in human-readable table format: + +```bash +az zones validate --output table +``` + +Validate all resources in specific resource groups to which you have read access: + +```bash +az zones validate --resource-groups ,,... +``` + +Omit 'dependent' resources from the output. These are resources that by themselves cannot be zone redundant, but take on the status of their parent or related resource. This can be useful for improving readability of the results: + +```bash +az zones validate --omit-dependent-resources +``` + +## Important Notes + +- The _zones_ CLI extension can only help with resources you can view, i.e. for which you have read access. You must ensure that all relevant resources are indeed listed in the results. + +- While this extension is a useful tool in validating zone redundancy on resources, you are still responsible for reviewing the [Reliability Guides](https://learn.microsoft.com/azure/reliability/overview-reliability-guidance) for all the services you use in your applications, as these may contain important information regarding operation in high availability scenarios. \ No newline at end of file diff --git a/src/zones/README.rst b/src/zones/README.rst deleted file mode 100644 index 4c34024348b..00000000000 --- a/src/zones/README.rst +++ /dev/null @@ -1,26 +0,0 @@ -Microsoft Azure CLI 'zones' Extension -========================================== - -This package is for the 'zones' extension. -i.e. 'az zones' - -This CLI Extension helps validate the zone redundancy status of resources within a specific scope. -For each resource, one of the following statuses will be returned: - Unknown # Unable to verify status. You'll need to check the resource manually. - Yes # Resource is configured for zone redundancy - Always # Resource is always zone redundant, no configuration needed - No # Resource is not configured for zone redundancy, but could be in another configuration - Never # Resource cannot be configured for zone redundancy - Dependent # Resource is zone redundant if parent or related resource is zone redundant - NoZonesInRegion # The region the resource is deployed in does not have Availability Zones - -USAGE - -Validate all resources in current scope to which you have read access: -az zones validate - -Validate all resources in specific resource groups to which you have read access: -az zones validate [--resource-groups , ...] - -Omit 'dependent' resources from the output: -az zones validate --omit-dependent-resources \ No newline at end of file From 0cc3b737ebb6298f5c1a95e7c6f55c4f2fecb3b9 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Fri, 25 Apr 2025 13:24:19 +0200 Subject: [PATCH 12/40] bug fixes and new resource types --- .../microsoft_apimanagement.py | 4 ++-- .../resource_type_validators/microsoft_app.py | 3 ++- .../microsoft_cache.py | 9 ++++--- .../microsoft_compute.py | 9 ++++--- .../microsoft_containerregistry.py | 3 +++ .../microsoft_containerservice.py | 6 ++--- .../microsoft_dbforpostgresql.py | 24 +++++++++++++++++++ .../microsoft_network.py | 5 +++- 8 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py index ce890dc2ffb..de4380f5b59 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py @@ -21,8 +21,8 @@ def validate(resource): case 'service': # API Management instances are zone redundant if they are premium and have more than one zone # https://learn.microsoft.com/azure/api-management/high-availability#availability-zones - return ZoneRedundancyValidationResult.Yes if resource['zones'] not in [None, []] \ - and len(resource['zones']) > 1 \ + zones = resource.get('zones') or [] + return ZoneRedundancyValidationResult.Yes if len(zones) > 1 \ and resource['sku']['name'] == 'Premium' \ else ZoneRedundancyValidationResult.No diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_app.py b/src/zones/azext_zones/resource_type_validators/microsoft_app.py index cf5034e1181..847aa08ee80 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_app.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_app.py @@ -21,6 +21,7 @@ def validate(resource): case 'managedenvironments': # Managed Environments are zone redundant if the zoneRedundant property is set to true # https://learn.microsoft.com/azure/reliability/reliability-azure-container-apps#availability-zone-support - return ZoneRedundancyValidationResult.Yes if resource['zoneRedundant'] == 'true' else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if resource['properties'].get('zoneRedundant', {}) == True \ + else ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py index 00991ded2e0..b1a7ab74e7f 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py @@ -17,14 +17,13 @@ def validate(resource): case 'redis': # Redis caches are zone redundant if they are premium SKU and have more than one zone set # https://learn.microsoft.com/azure/azure-cache-for-redis/cache-high-availability#zone-redundancy - return ZoneRedundancyValidationResult.Yes if resource['zones'] is not None \ - and len(resource['zones']) > 1 \ - and resource['sku']['name'] == 'Premium' \ + zones = resource.get('zones') or [] + return ZoneRedundancyValidationResult.Yes if len(zones) > 1 \ else ZoneRedundancyValidationResult.No case 'redisenterprise': - return ZoneRedundancyValidationResult.Yes if resource['zones'] is not None \ - and len(resource['zones']) > 1 \ + zones = resource.get('zones') or [] + return ZoneRedundancyValidationResult.Yes if len(zones) > 1 \ else ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py index 4412ffd6aaf..32f9901128d 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py @@ -15,15 +15,18 @@ def validate(resource): match resourceSubType: case 'disks': - return ZoneRedundancyValidationResult.Yes if resource['zones'] is not None and len(resource['zones']) > 1 else ZoneRedundancyValidationResult.No + zones = resource.get('zones') or [] + return ZoneRedundancyValidationResult.Yes if len(zones) > 1 else ZoneRedundancyValidationResult.No case 'virtualmachinescalesets': # VMSS is ZR if deployed to more than one zone - return ZoneRedundancyValidationResult.Yes if resource['zones'] is not None and len(resource['zones']) > 1 else ZoneRedundancyValidationResult.No + zones = resource.get('zones') or [] + return ZoneRedundancyValidationResult.Yes if len(zones) > 1 else ZoneRedundancyValidationResult.No case 'virtualmachines': # VM is ZR if deployed to more than one zone - return ZoneRedundancyValidationResult.Yes if resource['zones'] is not None and len(resource['zones']) > 1 else ZoneRedundancyValidationResult.No + zones = resource.get('zones') or [] + return ZoneRedundancyValidationResult.Yes if len(zones) > 1 else ZoneRedundancyValidationResult.No case 'virtualmachines/extensions': # VM extensions are zone redundant if the VM they are attached to is zone redundant diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py index da3fe84bc72..7707a8a5af1 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py @@ -19,4 +19,7 @@ def validate(resource): # https://learn.microsoft.com/azure/container-registry/zone-redundancy return ZoneRedundancyValidationResult.Yes if resource['properties']['zoneRedundancy'] == 'Enabled' else ZoneRedundancyValidationResult.No + case 'registries/replications': + return ZoneRedundancyValidationResult.Dependent + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py index 37f68766dac..3732998d1eb 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py @@ -18,8 +18,8 @@ def validate(resource): # AKS clusters are zone redundant if the node pools are spread across multiple zones # Zone Redundancy on AKS involves a lot of configuration steps, testing is required beyond this script. # https://learn.microsoft.com/azure/aks/availability-zones-overview - poolZones = resource['properties']['agentPoolProfiles'][0]['availabilityZones'] - poolZoneCount = len(poolZones) if poolZones else 0 - return ZoneRedundancyValidationResult.Yes if poolZoneCount > 1 == 'Enabled' else ZoneRedundancyValidationResult.No + poolZones = resource['properties']['agentPoolProfiles'][0].get('availabilityZones') or [] + poolZoneCount = len(poolZones) + return ZoneRedundancyValidationResult.Yes if poolZoneCount > 1 else ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py new file mode 100644 index 00000000000..ce628bdbf79 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py @@ -0,0 +1,24 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.dbforpostgresql') +class microsoft_dbforpostgresql: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_dbforpostgresql") + _logger.debug("Validating Microsoft.dbforpostgresql resource type: %s", resourceSubType) + + match resourceSubType: + case 'flexibleservers': + return ( + ZoneRedundancyValidationResult.Yes + if resource['properties'].get('highAvailability', {}).get('mode',{}) == 'ZoneRedundant' + else ZoneRedundancyValidationResult.No + ) + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_network.py b/src/zones/azext_zones/resource_type_validators/microsoft_network.py index f9b823c8cb7..679a5fb7ff2 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_network.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_network.py @@ -33,7 +33,10 @@ def validate(resource): return ZoneRedundancyValidationResult.Always case 'loadbalancers': - return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] in ['Standard'] and resource['frontendipconfigurations'][0].zones not in [None, []] else ZoneRedundancyValidationResult.No + frontend_ip_configs = resource.get('frontendipconfigurations', []) + zones = frontend_ip_configs[0].get('zones') if frontend_ip_configs else None + is_standard_sku = resource.get('sku', {}).get('name') == 'Standard' + return ZoneRedundancyValidationResult.Yes if is_standard_sku and zones else ZoneRedundancyValidationResult.No case 'localnetworkgateways': # Local network gateways depend on the configuration of the VPN Gateway From 35e1c89b0a590df7cbfd9c18e289e2e10509a2f0 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Fri, 25 Apr 2025 14:45:10 +0200 Subject: [PATCH 13/40] more resource types --- src/zones/azext_zones/custom.py | 9 ++++++- .../microsoft_documentdb.py | 22 +++++++++++++++++ .../microsoft_network.py | 16 +++++++------ .../microsoft_notificationhubs.py | 22 +++++++++++++++++ .../microsoft_recoveryservices.py | 22 +++++++++++++++++ .../microsoft_search.py | 24 +++++++++++++++++++ .../microsoft_servicebus.py | 21 ++++++++++++++++ .../microsoft_signalrservice.py | 22 +++++++++++++++++ .../resource_type_validators/microsoft_web.py | 24 +++++++++++++++++++ 9 files changed, 174 insertions(+), 8 deletions(-) create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_notificationhubs.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_search.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_web.py diff --git a/src/zones/azext_zones/custom.py b/src/zones/azext_zones/custom.py index a7c96bb61c1..b55ca84d473 100644 --- a/src/zones/azext_zones/custom.py +++ b/src/zones/azext_zones/custom.py @@ -48,7 +48,14 @@ def validate_resources(resources, omit_dependent_resources=False): zrStatus = ZoneRedundancyValidationResult.NoZonesInRegion else: validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) - zrStatus = ZoneRedundancyValidationResult.Unknown if validator is None else validator.validate(resource) + if validator is None: + zrStatus = ZoneRedundancyValidationResult.Unknown + else: + try: + zrStatus = validator.validate(resource) + except Exception as e: + __logger.warning(f"An error occurred when validating {resource.get('name', '')}: {e}\nPlease check the resource manually.") + zrStatus = ZoneRedundancyValidationResult.Unknown if zrStatus is not ZoneRedundancyValidationResult.Dependent or not omit_dependent_resources: resource_result = {} diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py new file mode 100644 index 00000000000..5b7303483be --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py @@ -0,0 +1,22 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.documentdb') +class microsoft_documentdb: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_documentdb") + _logger.debug("Validating Microsoft.documentdb resource type: %s", resourceSubType) + + match resourceSubType: + case 'databaseaccounts': + # https://learn.microsoft.com/en-us/azure/reliability/reliability-cosmos-db-nosql + # CosmosDB databases are zone redundant if then have the setting enabled on the region + return ZoneRedundancyValidationResult.Yes if resource['properties']['locations'][0]['isZoneRedundant'] else ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_network.py b/src/zones/azext_zones/resource_type_validators/microsoft_network.py index 679a5fb7ff2..a645d357afb 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_network.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_network.py @@ -15,10 +15,12 @@ def validate(resource): match resourceSubType: case 'applicationgateways': - return ZoneRedundancyValidationResult.Yes if resource['zones'] not in [None, []] else ZoneRedundancyValidationResult.No + zones = resource.get('zones') or [] + return ZoneRedundancyValidationResult.Yes if len(zones) > 1 else ZoneRedundancyValidationResult.No case 'azurefirewalls': - return ZoneRedundancyValidationResult.Yes if resource['zones'] not in [None, []] and resource['sku']['capacity'] > 1 else ZoneRedundancyValidationResult.No + zones = resource.get('zones') or [] + return ZoneRedundancyValidationResult.Yes if len(zones) > 1 and resource['sku']['capacity'] > 1 else ZoneRedundancyValidationResult.No case 'connections': # Network connections depend on the configuration of the Virtual Network Gateway @@ -33,10 +35,9 @@ def validate(resource): return ZoneRedundancyValidationResult.Always case 'loadbalancers': - frontend_ip_configs = resource.get('frontendipconfigurations', []) - zones = frontend_ip_configs[0].get('zones') if frontend_ip_configs else None - is_standard_sku = resource.get('sku', {}).get('name') == 'Standard' - return ZoneRedundancyValidationResult.Yes if is_standard_sku and zones else ZoneRedundancyValidationResult.No + frontend_ip_configs = resource['properties'].get('frontendIPConfigurations') or [] + zones = frontend_ip_configs[0].get('zones') or [] + return ZoneRedundancyValidationResult.Yes if len(zones) > 1 else ZoneRedundancyValidationResult.No case 'localnetworkgateways': # Local network gateways depend on the configuration of the VPN Gateway @@ -65,7 +66,8 @@ def validate(resource): return ZoneRedundancyValidationResult.Always case 'publicipaddresses': - return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] in ['Standard'] and resource['zones'] not in [None, []] else ZoneRedundancyValidationResult.No + zones = resource.get('zones') or [] + return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] in ['Standard'] and len(zones) > 1 else ZoneRedundancyValidationResult.No case 'virtualnetworks': # Virtual networks span all availability zones in a region. diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_notificationhubs.py b/src/zones/azext_zones/resource_type_validators/microsoft_notificationhubs.py new file mode 100644 index 00000000000..61bb9d08b2f --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_notificationhubs.py @@ -0,0 +1,22 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.notificationhubs') +class microsoft_notificationhubs: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_notificationhubs") + _logger.debug("Validating Microsoft.notificationhubs resource type: %s", resourceSubType) + + match resourceSubType: + case 'namespaces': + # In a region that supports availability zones, Notification Hubs supports a zone-redundant deployment by default. + # https://learn.microsoft.com/azure/reliability/reliability-notification-hubs#availability-zone-support + return ZoneRedundancyValidationResult.Yes if resource['properties'].get('zoneRedundancy', '') == 'Enabled' else ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py b/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py new file mode 100644 index 00000000000..eebdb702b56 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py @@ -0,0 +1,22 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.recoveryservices') +class microsoft_recoveryservices: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_recoveryservices") + _logger.debug("Validating Microsoft.recoveryservices resource type: %s", resourceSubType) + + match resourceSubType: + case 'vaults': + # https://learn.microsoft.com/azure/reliability/reliability-backup#availability-zone-support + # Recovery Services vaults are zone redundant if the storage redundancy was set to ZoneRedundant + return ZoneRedundancyValidationResult.Yes if resource['redundancySettings']['standardTierStorageRedundancy'] == 'ZoneRedundant' else ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_search.py b/src/zones/azext_zones/resource_type_validators/microsoft_search.py new file mode 100644 index 00000000000..7af6bd37a51 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_search.py @@ -0,0 +1,24 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.search') +class microsoft_search: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_search") + _logger.debug("Validating Microsoft.search resource type: %s", resourceSubType) + + match resourceSubType: + case 'searchservices': + # Standard or higher tiers in supported regions are zone redundant if the replica count is greater than 1. + # https://learn.microsoft.com/azure/search/search-reliability#availability-zone-support + sku = resource['sku']['name'] or '' + replicaCount = resource['properties'].get('replicaCount', 0) + return ZoneRedundancyValidationResult.Yes if sku not in ['Free', 'Basic'] and replicaCount > 1 else ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py b/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py new file mode 100644 index 00000000000..7e9dd1e0271 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py @@ -0,0 +1,21 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.servicebus') +class microsoft_servicebus: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_servicebus") + _logger.debug("Validating Microsoft.servicebus resource type: %s", resourceSubType) + + match resourceSubType: + case 'namespaces': + # servicebus namespaces are always zone redundant + return ZoneRedundancyValidationResult.Always + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py new file mode 100644 index 00000000000..07e6d30f653 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py @@ -0,0 +1,22 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.signalrservice') +class microsoft_signalrservice: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_signalrservice") + _logger.debug("Validating Microsoft.signalrservice resource type: %s", resourceSubType) + + match resourceSubType: + case 'signalr': + # SignalR is zone redundant by default on premium tiers + # https://learn.microsoft.com/azure/azure-signalr/availability-zones + return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] == 'Premium' else ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_web.py b/src/zones/azext_zones/resource_type_validators/microsoft_web.py new file mode 100644 index 00000000000..5d70cf864d2 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_web.py @@ -0,0 +1,24 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.web') +class microsoft_web: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_web") + _logger.debug("Validating Microsoft.web resource type: %s", resourceSubType) + + match resourceSubType: + case 'serverfarms': + # App Service Plans are zone redundant if they have zone redundancy enabled and have more than one instance + # https://learn.microsoft.com/azure/reliability/reliability-app-service?pivots=free-shared-basic#availability-zone-support + zrEnabled = resource['properties'].get('zoneRedundant', False) + instanceCount = resource['sku'].get('capacity', 0) + return ZoneRedundancyValidationResult.Yes if zrEnabled and instanceCount > 1 else ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file From 605c4971b70424288828620bc833b1f587c3a2de Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Fri, 25 Apr 2025 14:54:36 +0200 Subject: [PATCH 14/40] temp remove notificationhubs --- .../microsoft_notificationhubs.py | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_notificationhubs.py diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_notificationhubs.py b/src/zones/azext_zones/resource_type_validators/microsoft_notificationhubs.py deleted file mode 100644 index 61bb9d08b2f..00000000000 --- a/src/zones/azext_zones/resource_type_validators/microsoft_notificationhubs.py +++ /dev/null @@ -1,22 +0,0 @@ -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type -from knack.log import get_logger - - -@register_resource_type('microsoft.notificationhubs') -class microsoft_notificationhubs: - - @staticmethod - def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] - - _logger = get_logger("microsoft_notificationhubs") - _logger.debug("Validating Microsoft.notificationhubs resource type: %s", resourceSubType) - - match resourceSubType: - case 'namespaces': - # In a region that supports availability zones, Notification Hubs supports a zone-redundant deployment by default. - # https://learn.microsoft.com/azure/reliability/reliability-notification-hubs#availability-zone-support - return ZoneRedundancyValidationResult.Yes if resource['properties'].get('zoneRedundancy', '') == 'Enabled' else ZoneRedundancyValidationResult.No - - return ZoneRedundancyValidationResult.Unknown From 0234decd33b4f4d155af1a503415077495920072 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Fri, 25 Apr 2025 16:27:03 +0200 Subject: [PATCH 15/40] add more resource types --- .../microsoft_eventgrid.py | 18 ++++++++++++++ .../microsoft_eventhub.py | 22 +++++++++++++++++ .../microsoft_hdinsight.py | 17 +++++++++++++ .../microsoft_insights.py | 17 +++++++++++++ .../microsoft_kusto.py | 24 +++++++++++++++++++ .../microsoft_loadtestservice.py | 17 +++++++++++++ .../microsoft_machinelearningservices.py | 20 ++++++++++++++++ .../microsoft_managedidentity.py | 17 +++++++++++++ .../microsoft_maps.py | 17 +++++++++++++ .../microsoft_operationalinsights.py | 22 +++++++++++++++++ 10 files changed, 191 insertions(+) create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_insights.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_kusto.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_maps.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py b/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py new file mode 100644 index 00000000000..b9e8600f9cc --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py @@ -0,0 +1,18 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.eventgrid') +class microsoft_eventgrid: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_eventgrid") + _logger.debug("Validating Microsoft.eventgrid resource type: %s", resourceSubType) + + # EventGrid resources are zone redundant by default + # https://learn.microsoft.com/azure/reliability/reliability-event-grid#availability-zone-support return ZoneRedundancyValidationResult.Always + return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py b/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py new file mode 100644 index 00000000000..67d4e303c13 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py @@ -0,0 +1,22 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.eventhub') +class microsoft_eventhub: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_eventhub") + _logger.debug("Validating Microsoft.eventhub resource type: %s", resourceSubType) + + match resourceSubType: + case 'namespaces': + # If you create an Event Hubs namespace in a region that supports availability zones, zone redundancy is automatically enabled. + # https://learn.microsoft.com/azure/reliability/reliability-event-hubs#availability-zone-support + return ZoneRedundancyValidationResult.Always + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py b/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py new file mode 100644 index 00000000000..6dcdb4f2f89 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py @@ -0,0 +1,17 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.hdinsight') +class microsoft_hdinsight: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_hdinsight") + _logger.debug("Validating Microsoft.hdinsight resource type: %s", resourceSubType) + + # HDInsight clusters are zonal resources. They exist in a single zone. + return ZoneRedundancyValidationResult.Never diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_insights.py b/src/zones/azext_zones/resource_type_validators/microsoft_insights.py new file mode 100644 index 00000000000..83b6191e6b5 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_insights.py @@ -0,0 +1,17 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.insights') +class microsoft_insights: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_insights") + _logger.debug("Validating Microsoft.insights resource type: %s", resourceSubType) + + # insights resources are zone redundant by default + return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py new file mode 100644 index 00000000000..8596e43d531 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py @@ -0,0 +1,24 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.kusto') +class microsoft_kusto: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_kusto") + _logger.debug("Validating Microsoft.kusto resource type: %s", resourceSubType) + + match resourceSubType: + case 'clusters': + # AKS clusters are zone redundant if the node pools are spread across multiple zones + # Zone Redundancy on AKS involves a lot of configuration steps, testing is required beyond this script. + # https://learn.microsoft.com/azure/aks/availability-zones-overview + zones = resource.get('zones') or [] + return ZoneRedundancyValidationResult.Yes if len(zones) > 1 else ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py new file mode 100644 index 00000000000..78525ce6f81 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py @@ -0,0 +1,17 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.loadtestservice') +class microsoft_loadtestservice: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_loadtestservice") + _logger.debug("Validating Microsoft.loadtestservice resource type: %s", resourceSubType) + + # loadtestservice resources are never zone redundant + return ZoneRedundancyValidationResult.Never diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py b/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py new file mode 100644 index 00000000000..0b94c1645ed --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py @@ -0,0 +1,20 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.machinelearningservices') +class microsoft_machinelearningservices: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_machinelearningservices") + _logger.debug("Validating Microsoft.machinelearningservices resource type: %s", resourceSubType) + + match resourceSubType: + case 'workspaces': + return ZoneRedundancyValidationResult.Never + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py b/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py new file mode 100644 index 00000000000..125e66fa945 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py @@ -0,0 +1,17 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.managedidentity') +class microsoft_managedidentity: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_managedidentity") + _logger.debug("Validating Microsoft.managedidentity resource type: %s", resourceSubType) + + # managedidentity resources are zone redundant by default + return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_maps.py b/src/zones/azext_zones/resource_type_validators/microsoft_maps.py new file mode 100644 index 00000000000..e97f5459d2c --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_maps.py @@ -0,0 +1,17 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.maps') +class microsoft_maps: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_maps") + _logger.debug("Validating Microsoft.maps resource type: %s", resourceSubType) + + # maps resources are zone redundant by default + return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py b/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py new file mode 100644 index 00000000000..6eb516ce45b --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py @@ -0,0 +1,22 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.operationalinsights') +class microsoft_operationalinsights: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_operationalinsights") + _logger.debug("Validating Microsoft.operationalinsights resource type: %s", resourceSubType) + + match resourceSubType: + case 'workspaces': + # Operational Insights workspaces are zone redundant by default, + # Note: Operational Insights workspaces are zone redundant by default only in some regions. Check https://learn.microsoft.com/azure/azure-monitor/logs/availability-zones. + return ZoneRedundancyValidationResult.Always + + return ZoneRedundancyValidationResult.Unknown \ No newline at end of file From be94d39c147f087d1f03d9fb2457fe9627116384 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Mon, 28 Apr 2025 11:42:59 +0200 Subject: [PATCH 16/40] adding tests and bugfixes --- .../microsoft_apimanagement.py | 2 +- .../microsoft_automation.py | 9 +- .../resource_type_validators/microsoft_cdn.py | 7 +- .../microsoft_chaos.py | 9 +- .../microsoft_compute.py | 2 +- .../microsoft_containerregistry.py | 2 +- .../microsoft_containerservice.py | 2 +- .../microsoft_dbforpostgresql.py | 2 +- .../microsoft_documentdb.py | 2 +- .../microsoft_eventhub.py | 9 +- .../microsoft_kusto.py | 2 +- .../microsoft_recoveryservices.py | 2 +- .../microsoft_search.py | 2 +- .../resource_type_validators/microsoft_web.py | 4 + .../latest/test_microsoft_apimanagement.py | 57 ++++++ .../tests/latest/test_microsoft_app.py | 50 +++++ .../tests/latest/test_microsoft_cache.py | 48 +++++ .../tests/latest/test_microsoft_compute.py | 100 ++++++++++ .../test_microsoft_containerregistry.py | 49 +++++ .../latest/test_microsoft_containerservice.py | 58 ++++++ .../latest/test_microsoft_dbforpostgresql.py | 53 ++++++ .../tests/latest/test_microsoft_documentdb.py | 56 ++++++ .../tests/latest/test_microsoft_kusto.py | 48 +++++ .../tests/latest/test_microsoft_network.py | 179 ++++++++++++++++++ .../latest/test_microsoft_recoveryservices.py | 52 +++++ .../tests/latest/test_microsoft_search.py | 54 ++++++ .../latest/test_microsoft_signalrservice.py | 50 +++++ .../tests/latest/test_microsoft_sql.py | 48 +++++ .../tests/latest/test_microsoft_storage.py | 48 +++++ .../tests/latest/test_microsoft_web.py | 55 ++++++ .../tests/latest/test_zones_scenario.py | 40 ---- 31 files changed, 1029 insertions(+), 72 deletions(-) create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_apimanagement.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_app.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_cache.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_compute.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_containerregistry.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_containerservice.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_dbforpostgresql.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_documentdb.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_kusto.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_network.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_recoveryservices.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_search.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_signalrservice.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_sql.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_storage.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_web.py delete mode 100644 src/zones/azext_zones/tests/latest/test_zones_scenario.py diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py index de4380f5b59..8b670090d33 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py @@ -12,7 +12,7 @@ def validate(resource): _logger = get_logger("microsoft_apimanagement") _logger.debug("Validating Microsoft.apimanagement resource type: %s", resourceSubType) - + match resourceSubType: case 'gateways': # ZR state of the gateway is defined on the service level diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_automation.py b/src/zones/azext_zones/resource_type_validators/microsoft_automation.py index ea7c49bd589..7c108eee494 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_automation.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_automation.py @@ -13,10 +13,7 @@ def validate(resource): _logger = get_logger("microsoft_automation") _logger.debug("Validating Microsoft.automation resource type: %s", resourceSubType) - match resourceSubType: - case 'automationaccounts': - # Automation accounts are zone redundant by default - # https://learn.microsoft.com/azure/automation/automation-availability-zones - return ZoneRedundancyValidationResult.Always - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + # Automation accounts are zone redundant by default + # https://learn.microsoft.com/azure/automation/automation-availability-zones + return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py b/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py index de09b2199cc..dd0d87f4097 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py @@ -13,9 +13,6 @@ def validate(resource): _logger = get_logger("microsoft_cdn") _logger.debug("Validating Microsoft.cdn resource type: %s", resourceSubType) - match resourceSubType: - case 'profiles': - # Cdn profiles are a global service and are zone redundant by default - return ZoneRedundancyValidationResult.Always - return ZoneRedundancyValidationResult.Unknown + # Cdn profiles are a global service and are zone redundant by default + return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py b/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py index 48f8aad42b3..6ff4dcbaf61 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py @@ -13,10 +13,7 @@ def validate(resource): _logger = get_logger("microsoft_chaos") _logger.debug("Validating Microsoft.chaos resource type: %s", resourceSubType) - match resourceSubType: - case 'experiments': - # chaos profiles are always zone redundant - # https://learn.microsoft.com/azure/reliability/reliability-chaos-studio#availability-zone-support - return ZoneRedundancyValidationResult.Always - return ZoneRedundancyValidationResult.Unknown + # chaos profiles are always zone redundant + # https://learn.microsoft.com/azure/reliability/reliability-chaos-studio#availability-zone-support + return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py index 32f9901128d..461bc967e19 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py @@ -12,7 +12,7 @@ def validate(resource): _logger = get_logger("microsoft_compute") _logger.debug("Validating Microsoft.Compute resource type: %s", resourceSubType) - + match resourceSubType: case 'disks': zones = resource.get('zones') or [] diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py index 7707a8a5af1..d871b701640 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py @@ -12,7 +12,7 @@ def validate(resource): _logger = get_logger("microsoft_containerregistry") _logger.debug("Validating Microsoft.containerregistry resource type: %s", resourceSubType) - + match resourceSubType: case 'registries': # Container registries are zone redundant if the setting enabled diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py index 3732998d1eb..d83ee1d3895 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py @@ -12,7 +12,7 @@ def validate(resource): _logger = get_logger("microsoft_containerservice") _logger.debug("Validating Microsoft.containerservice resource type: %s", resourceSubType) - + match resourceSubType: case 'managedclusters': # AKS clusters are zone redundant if the node pools are spread across multiple zones diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py index ce628bdbf79..7a7199c74da 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py @@ -12,7 +12,7 @@ def validate(resource): _logger = get_logger("microsoft_dbforpostgresql") _logger.debug("Validating Microsoft.dbforpostgresql resource type: %s", resourceSubType) - + match resourceSubType: case 'flexibleservers': return ( diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py index 5b7303483be..d4098775e5f 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py @@ -12,7 +12,7 @@ def validate(resource): _logger = get_logger("microsoft_documentdb") _logger.debug("Validating Microsoft.documentdb resource type: %s", resourceSubType) - + match resourceSubType: case 'databaseaccounts': # https://learn.microsoft.com/en-us/azure/reliability/reliability-cosmos-db-nosql diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py b/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py index 67d4e303c13..4d4401e1c02 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py @@ -13,10 +13,7 @@ def validate(resource): _logger = get_logger("microsoft_eventhub") _logger.debug("Validating Microsoft.eventhub resource type: %s", resourceSubType) - match resourceSubType: - case 'namespaces': - # If you create an Event Hubs namespace in a region that supports availability zones, zone redundancy is automatically enabled. - # https://learn.microsoft.com/azure/reliability/reliability-event-hubs#availability-zone-support - return ZoneRedundancyValidationResult.Always - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + # If you create an Event Hubs namespace in a region that supports availability zones, zone redundancy is automatically enabled. + # https://learn.microsoft.com/azure/reliability/reliability-event-hubs#availability-zone-support + return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py index 8596e43d531..8043c738821 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py @@ -12,7 +12,7 @@ def validate(resource): _logger = get_logger("microsoft_kusto") _logger.debug("Validating Microsoft.kusto resource type: %s", resourceSubType) - + match resourceSubType: case 'clusters': # AKS clusters are zone redundant if the node pools are spread across multiple zones diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py b/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py index eebdb702b56..1b646cba87d 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py @@ -17,6 +17,6 @@ def validate(resource): case 'vaults': # https://learn.microsoft.com/azure/reliability/reliability-backup#availability-zone-support # Recovery Services vaults are zone redundant if the storage redundancy was set to ZoneRedundant - return ZoneRedundancyValidationResult.Yes if resource['redundancySettings']['standardTierStorageRedundancy'] == 'ZoneRedundant' else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if resource['properties']['redundancySettings']['standardTierStorageRedundancy'] == 'ZoneRedundant' else ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_search.py b/src/zones/azext_zones/resource_type_validators/microsoft_search.py index 7af6bd37a51..3577da1f76a 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_search.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_search.py @@ -12,7 +12,7 @@ def validate(resource): _logger = get_logger("microsoft_search") _logger.debug("Validating Microsoft.search resource type: %s", resourceSubType) - + match resourceSubType: case 'searchservices': # Standard or higher tiers in supported regions are zone redundant if the replica count is greater than 1. diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_web.py b/src/zones/azext_zones/resource_type_validators/microsoft_web.py index 5d70cf864d2..42e4df4023b 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_web.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_web.py @@ -20,5 +20,9 @@ def validate(resource): zrEnabled = resource['properties'].get('zoneRedundant', False) instanceCount = resource['sku'].get('capacity', 0) return ZoneRedundancyValidationResult.Yes if zrEnabled and instanceCount > 1 else ZoneRedundancyValidationResult.No + + case 'sites': + # Web Apps are zone redundant if they are hosted on a zone redundant App Service Plan + return ZoneRedundancyValidationResult.Dependent return ZoneRedundancyValidationResult.Unknown \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_apimanagement.py b/src/zones/azext_zones/tests/latest/test_microsoft_apimanagement.py new file mode 100644 index 00000000000..c74a81eaa3f --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_apimanagement.py @@ -0,0 +1,57 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_apimanagement(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.apimanagement/service", + "sku": { + "name": "Premium", + "capacity": 3 + }, + "zones": [ + "1", + "2", + "3" + ] + } + + resource_nonzr = \ + { + "type": "microsoft.apimanagement/service", + "sku": { + "name": "Premium", + "capacity": 1 + }, + "tags": {}, + "zones": None + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_apimanagement, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_app.py b/src/zones/azext_zones/tests/latest/test_microsoft_app.py new file mode 100644 index 00000000000..69bb761dba1 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_app.py @@ -0,0 +1,50 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_app(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.app/managedenvironments", + "resourceGroup": "testResourceGroup", + "properties": { + "zoneRedundant": True, + }, + } + + resource_nonzr = \ + { + "type": "microsoft.app/managedenvironments", + "resourceGroup": "testResourceGroup", + "properties": { + "zoneRedundant": False, + }, + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_app, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_cache.py b/src/zones/azext_zones/tests/latest/test_microsoft_cache.py new file mode 100644 index 00000000000..6935778e9ce --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_cache.py @@ -0,0 +1,48 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_cache(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.cache/redis", + "zones": [ + "1", + "2", + "3" + ] + } + + resource_nonzr = \ + { + "type": "microsoft.cache/redis", + "zones": None + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_cache, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_compute.py b/src/zones/azext_zones/tests/latest/test_microsoft_compute.py new file mode 100644 index 00000000000..539dc8a3e7c --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_compute.py @@ -0,0 +1,100 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_app(ScenarioTest): + + resource_disk_zr = \ + { + "type": "microsoft.compute/disks", + "zones": [ + "1", + "2", + "3" + ] + } + + resource_disk_nonzr = \ + { + "type": "microsoft.compute/disks", + "zones": None + } + + resource_vmss_zr = \ + { + "type": "microsoft.compute/virtualmachinescalesets", + "zones": [ + "1", + "2", + "3" + ] + } + + resource_vmss_nonzr = \ + { + "type": "microsoft.compute/virtualmachinescalesets", + "zones": None + } + + resource_vm_zr = \ + { + "type": "microsoft.compute/virtualmachines", + "zones": [ + "1", + "2", + "3" + ] + } + + resource_vm_nonzr = \ + { + "type": "microsoft.compute/virtualmachines", + "zones": None + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_app, cls).setUpClass() + resourceProvider = cls.resource_disk_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_disk_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_disk_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_disk_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_disk_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) + + def test_vmss_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_vmss_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_vmss_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_vmss_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) + + def test_vm_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_vm_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_vm_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_vm_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_containerregistry.py b/src/zones/azext_zones/tests/latest/test_microsoft_containerregistry.py new file mode 100644 index 00000000000..be350a1c017 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_containerregistry.py @@ -0,0 +1,49 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_containerregistry(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.containerregistry/registries", + "properties": { + "zoneRedundancy": "Enabled" + } + } + + + resource_nonzr = \ + { + "type": "microsoft.containerregistry/registries", + "properties": { + "zoneRedundancy": "Disabled" + } + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_containerregistry, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_containerservice.py b/src/zones/azext_zones/tests/latest/test_microsoft_containerservice.py new file mode 100644 index 00000000000..f9c2f8e25d6 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_containerservice.py @@ -0,0 +1,58 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_containerservice(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.containerservice/managedclusters", + "properties": { + "agentPoolProfiles": [ + { + "availabilityZones": [ + "1", + "2", + "3" + ] + } + ] + } + } + + resource_nonzr = \ + { + "type": "microsoft.containerservice/managedclusters", + "properties": { + "agentPoolProfiles": [ + {} + ] + } + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_containerservice, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_dbforpostgresql.py b/src/zones/azext_zones/tests/latest/test_microsoft_dbforpostgresql.py new file mode 100644 index 00000000000..6a63bbf0498 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_dbforpostgresql.py @@ -0,0 +1,53 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_dbforpostgresql(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.dbforpostgresql/flexibleservers", + "properties": { + "highAvailability": { + "mode": "ZoneRedundant" + } + } + } + + resource_nonzr = \ + { + "type": "microsoft.dbforpostgresql/flexibleservers", + "properties": { + "highAvailability": { + "state": "NotEnabled", + "mode": "Disabled" + } + } + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_dbforpostgresql, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_documentdb.py b/src/zones/azext_zones/tests/latest/test_microsoft_documentdb.py new file mode 100644 index 00000000000..886c47538c0 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_documentdb.py @@ -0,0 +1,56 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_documentdb(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.documentdb/databaseaccounts", + "properties": { + "locations": [ + { + "isZoneRedundant": True + } + ] + } + } + + resource_nonzr = \ + { + "type": "microsoft.documentdb/databaseaccounts", + "properties": { + "locations": [ + { + "isZoneRedundant": False + } + ] + } + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_documentdb, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_kusto.py b/src/zones/azext_zones/tests/latest/test_microsoft_kusto.py new file mode 100644 index 00000000000..191b8d2aa78 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_kusto.py @@ -0,0 +1,48 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_kusto(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.kusto/clusters", + "zones": [ + "1", + "3", + "2" + ] + } + + resource_nonzr = \ + { + "type": "microsoft.kusto/clusters", + "zones": None + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_kusto, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_network.py b/src/zones/azext_zones/tests/latest/test_microsoft_network.py new file mode 100644 index 00000000000..ee87e61ae98 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_network.py @@ -0,0 +1,179 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_network(ScenarioTest): + + resource_applicationgateways_zr = \ + { + "type": "microsoft.network/applicationgateways", + "zones": [ + "1", + "2", + "3" + ] + } + + resource_applicationgateways_nonzr = \ + { + "type": "microsoft.network/applicationgateways", + "zones": None + } + + resource_azurefirewalls_zr = \ + { + "type": "microsoft.network/azurefirewalls", + "sku": { + "capacity": 3 + }, + "zones": [ + "1", + "2", + "3" + ] + } + + resource_azurefirewalls_nonzr = \ + { + "type": "microsoft.network/azurefirewalls", + "sku": { + "capacity": 1 + }, + "zones": None + } + + resource_loadbalancers_zr = \ + { + "type": "microsoft.network/loadbalancers", + "properties": { + "frontendIPConfigurations": [ + { + "zones": [ + "1", + "2", + "3" + ] + } + ] + } + } + resource_loadbalancers_nonzr = \ + { + "type": "microsoft.network/loadbalancers", + "properties": { + "frontendIPConfigurations": [ + { + "zones": None + } + ] + } + } + + resource_publicipaddresses_zr = \ + { + "type": "microsoft.network/publicipaddresses", + "sku": { + "name": "Standard" + }, + "zones": [ + "1", + "2", + "3" + ] + } + + resource_publicipaddresses_nonzr = \ + { + "type": "microsoft.network/publicipaddresses", + "sku": { + "name": "Basic" + }, + "zones": None + } + + resource_virtualnetworkgateways_zr = \ + { + "type": "microsoft.network/virtualnetworkgateways", + "properties": { + "sku": { + "name": "VpnGw2AZ", + } + } + } + + resource_virtualnetworkgateways_nonzr = \ + { + "type": "microsoft.network/virtualnetworkgateways", + "properties": { + "sku": { + "name": "VpnGw2", + } + } + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_network, cls).setUpClass() + resourceProvider = cls.resource_applicationgateways_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_applicationgateways_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_applicationgateways_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_applicationgateways_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_applicationgateways_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) + + def test_azurefirewalls_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_azurefirewalls_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_azurefirewalls_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_azurefirewalls_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) + + def test_loadbalancers_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_loadbalancers_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_loadbalancers_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_loadbalancers_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) + + def test_publicipaddresses_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_publicipaddresses_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_publicipaddresses_onzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_publicipaddresses_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) + + def test_virtualnetworkgateways_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_virtualnetworkgateways_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_virtualnetworkgateways_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_virtualnetworkgateways_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_recoveryservices.py b/src/zones/azext_zones/tests/latest/test_microsoft_recoveryservices.py new file mode 100644 index 00000000000..97f1e7dbb44 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_recoveryservices.py @@ -0,0 +1,52 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_recoveryservices(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.recoveryservices/vaults", + "properties": { + "redundancySettings": { + "standardTierStorageRedundancy": "ZoneRedundant", + } + } + } + + resource_nonzr = \ + { + "type": "microsoft.recoveryservices/vaults", + "properties": { + "redundancySettings": { + "standardTierStorageRedundancy": "LocallyRedundant", + } + } + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_recoveryservices, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_search.py b/src/zones/azext_zones/tests/latest/test_microsoft_search.py new file mode 100644 index 00000000000..5f6db71824b --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_search.py @@ -0,0 +1,54 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_search(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.search/searchservices", + "sku": { + "name": "standard" + }, + "properties": { + "replicaCount": 3 + } + } + + resource_nonzr = \ + { + "type": "microsoft.search/searchservices", + "sku": { + "name": "standard" + }, + "properties": { + "replicaCount": 1 + } + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_search, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_signalrservice.py b/src/zones/azext_zones/tests/latest/test_microsoft_signalrservice.py new file mode 100644 index 00000000000..d903601a2b2 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_signalrservice.py @@ -0,0 +1,50 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_signalrservice(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.signalrservice/signalr", + "sku": { + "name": "Premium" + }, + "zones": None + } + + resource_nonzr = \ + { + "type": "microsoft.signalrservice/signalr", + "sku": { + "name": "Standard" + }, + "zones": None + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_signalrservice, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_sql.py b/src/zones/azext_zones/tests/latest/test_microsoft_sql.py new file mode 100644 index 00000000000..e2c225c30fc --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_sql.py @@ -0,0 +1,48 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_sql(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.sql/servers/databases", + "properties": { + "zoneRedundant": True, + }, + } + + resource_nonzr = \ + { + "type": "microsoft.sql/servers/databases", + "properties": { + "zoneRedundant": False, + }, + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_sql, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_storage.py b/src/zones/azext_zones/tests/latest/test_microsoft_storage.py new file mode 100644 index 00000000000..2d9ae74394f --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_storage.py @@ -0,0 +1,48 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_storage(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.storage/storageaccounts", + "sku": { + "name": "Standard_ZRS", + } + } + + resource_nonzr = \ + { + "type": "microsoft.storage/storageaccounts", + "sku": { + "name": "Standard_LRS", + } + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_storage, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_web.py b/src/zones/azext_zones/tests/latest/test_microsoft_web.py new file mode 100644 index 00000000000..8b18e692975 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_web.py @@ -0,0 +1,55 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult + + +class test_microsoft_web(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.web/serverfarms", + "sku": { + "capacity": 2 + }, + "properties": { + "zoneRedundant": True + } + } + + resource_nonzr = \ + { + "type": "microsoft.web/serverfarms", + "sku": { + "capacity": 1 + }, + "properties": { + "zoneRedundant": False + } + } + + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_web, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_zones_scenario.py b/src/zones/azext_zones/tests/latest/test_zones_scenario.py deleted file mode 100644 index 775d74aae6a..00000000000 --- a/src/zones/azext_zones/tests/latest/test_zones_scenario.py +++ /dev/null @@ -1,40 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -import os -import unittest - -from azure_devtools.scenario_tests import AllowLargeResponse -from azure.cli.testsdk import (ScenarioTest, ResourceGroupPreparer) - - -TEST_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), '..')) - - -class ZonesScenarioTest(ScenarioTest): - - @ResourceGroupPreparer(name_prefix='cli_test_zones') - def test_zones(self, resource_group): - - self.kwargs.update({ - 'name': 'test1' - }) - - self.cmd('zones create -g {rg} -n {name} --tags foo=doo', checks=[ - self.check('tags.foo', 'doo'), - self.check('name', '{name}') - ]) - self.cmd('zones update -g {rg} -n {name} --tags foo=boo', checks=[ - self.check('tags.foo', 'boo') - ]) - count = len(self.cmd('zones list').get_output_in_json()) - self.cmd('zones show - {rg} -n {name}', checks=[ - self.check('name', '{name}'), - self.check('resourceGroup', '{rg}'), - self.check('tags.foo', 'boo') - ]) - self.cmd('zones delete -g {rg} -n {name}') - final_count = len(self.cmd('zones list').get_output_in_json()) - self.assertTrue(final_count, count - 1) \ No newline at end of file From 2d01598edb13da522be53cddebb106f7990f6e7e Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Mon, 28 Apr 2025 13:15:32 +0200 Subject: [PATCH 17/40] minor structural changes --- src/zones/azext_zones/_clients.py | 2 + src/zones/azext_zones/_locationDataHelper.py | 41 +++++++++++++++++ src/zones/azext_zones/_locationHelper.py | 45 ------------------- src/zones/azext_zones/_params.py | 1 + .../azext_zones/_resourceTypeValidation.py | 33 +++++++------- src/zones/azext_zones/custom.py | 42 ++++++++++------- src/zones/setup.py | 2 +- 7 files changed, 88 insertions(+), 78 deletions(-) create mode 100644 src/zones/azext_zones/_locationDataHelper.py delete mode 100644 src/zones/azext_zones/_locationHelper.py diff --git a/src/zones/azext_zones/_clients.py b/src/zones/azext_zones/_clients.py index 55759c9551a..efe709801fd 100644 --- a/src/zones/azext_zones/_clients.py +++ b/src/zones/azext_zones/_clients.py @@ -1,6 +1,8 @@ from azure.cli.core.util import send_raw_request from azure.cli.core.commands.client_factory import get_subscription_id + +# pylint: disable=too-few-public-methods class MgmtApiClient(): def query(self, cmd, method, resource, api_version, requestBody): diff --git a/src/zones/azext_zones/_locationDataHelper.py b/src/zones/azext_zones/_locationDataHelper.py new file mode 100644 index 00000000000..4fbe2a8c889 --- /dev/null +++ b/src/zones/azext_zones/_locationDataHelper.py @@ -0,0 +1,41 @@ +from ._clients import MgmtApiClient +from knack.log import get_logger + + +class LocationDataHelper: + + _location_data = None + _logger = None + + def __init__(self, cmd): + self.cmd = cmd + self._logger = get_logger(__name__) + + def fetch_location_data(self): + if not LocationDataHelper._location_data: + # query(cls, cmd, method, resource, api-version, requestBody): + LocationDataHelper._location_data = MgmtApiClient.query(self, + self.cmd, + "GET", + "locations", + "2022-12-01", + None + ) + + self._logger.debug("Loaded location data successfully.") + + def region_has_zones(self, region): + if LocationDataHelper._location_data is None: + return None + + # While 'global' is not a valid region, we want to return true for global resources + if region == 'global': + return True + + if LocationDataHelper._location_data: + location_data = LocationDataHelper._location_data.get('value', []) + for location in location_data: + if location['name'].lower() == region.lower(): + return 'availabilityZoneMappings' in location + + return None diff --git a/src/zones/azext_zones/_locationHelper.py b/src/zones/azext_zones/_locationHelper.py deleted file mode 100644 index 82db3e7c59b..00000000000 --- a/src/zones/azext_zones/_locationHelper.py +++ /dev/null @@ -1,45 +0,0 @@ -from ._clients import MgmtApiClient -from knack.log import get_logger - - -class LocationDataHelper: - - _location_data = None - _logger = None - - def __init__(self, cmd): - self.cmd = cmd - self._logger = get_logger(__name__) - - def get_location_data(self): - if not LocationDataHelper._location_data: - # query(cls, cmd, method, resource, api-version, requestBody): - try: - LocationDataHelper._location_data = MgmtApiClient.query(self, - self.cmd, - "GET", - "locations", - "2022-12-01", - None - ) - except Exception as e: - self._logger.warning( - f"An error occurred while querying location data: {e}. No location data will be used." - "Please validate manually if the regions used support Availability Zones.") - - self._logger.debug(f"Loaded location data successfully.") - - def region_has_zones(region): - if not LocationDataHelper._location_data: - return None - - # While 'global' is not a valid region, we want to return true for global resources - if region == 'global': - return True - - if LocationDataHelper._location_data: - for location in LocationDataHelper._location_data['value']: - if location['name'].lower() == region.lower(): - return 'availabilityZoneMappings' in location - - return None diff --git a/src/zones/azext_zones/_params.py b/src/zones/azext_zones/_params.py index 23a9721ccee..9844b86af16 100644 --- a/src/zones/azext_zones/_params.py +++ b/src/zones/azext_zones/_params.py @@ -6,6 +6,7 @@ from azure.cli.core.commands.parameters import get_three_state_flag + def load_arguments(self, _): with self.argument_context('zones validate') as c: diff --git a/src/zones/azext_zones/_resourceTypeValidation.py b/src/zones/azext_zones/_resourceTypeValidation.py index 62fe4a23465..3cbb0fd003f 100644 --- a/src/zones/azext_zones/_resourceTypeValidation.py +++ b/src/zones/azext_zones/_resourceTypeValidation.py @@ -14,34 +14,35 @@ def decorator(cls): # This is the factory class to get the appropriate validator based on resource type: -class ResourceTypeValidatorFactory: - def getValidator(resourceType): - validator_class = resource_type_validators.get(resourceType) - if validator_class: - return validator_class() - return None +def getResourceTypeValidator(resourceType): + validator_class = resource_type_validators.get(resourceType) + if validator_class: + return validator_class() + return None # This is the base class for all resource type validators: -class ResourceTypeValidator(ABC): +class ResourceTypeValidator(ABC): # pylint: disable=too-few-public-methods` @abstractmethod def validate(self): pass class ZoneRedundancyValidationResult(Enum): - Unknown = 1 # Unable to verify status - Yes = 2 # Resource is configured for zone redundancy - Always = 3 # Resource is always zone redundant - No = 4 # Resource is not configured for zone redundancy - Never = 5 # Resource cannot be configured for zone redundancy - Dependent = 6 # Resource is zone redundant if parent or related resource is zone redundant - NoZonesInRegion = 7 # Resource is not zone redundant because the region does not support zones - - + Unknown = 1 # Unable to verify status + Yes = 2 # Resource is configured for zone redundancy + Always = 3 # Resource is always zone redundant + No = 4 # Resource is not configured for zone redundancy + Never = 5 # Resource cannot be configured for zone redundancy + Dependent = 6 # Resource is zone redundant if parent or related resource is zone redundant + NoZonesInRegion = 7 # Resource is not zone redundant because the region does not support zones + + @staticmethod def to_string(value): try: + # Attempt to create an enum member from the value result = ZoneRedundancyValidationResult(value) return result.name except ValueError: + # If the value doesn't correspond to any enum member, return "Unknown" return "Unknown" diff --git a/src/zones/azext_zones/custom.py b/src/zones/azext_zones/custom.py index b55ca84d473..dc0a00a5fe6 100644 --- a/src/zones/azext_zones/custom.py +++ b/src/zones/azext_zones/custom.py @@ -4,19 +4,14 @@ # -------------------------------------------------------------------------------------------- from knack.log import get_logger -from ._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult from ._argHelper import build_arg_query, execute_arg_query -from ._locationHelper import LocationDataHelper +from ._locationDataHelper import LocationDataHelper __logger = get_logger(__name__) def validate_zones(client, cmd, omit_dependent_resources, resource_group_names): - - # Get the location data we'll use to validate the resources - location_data_helper = LocationDataHelper(cmd) - location_data_helper.get_location_data() - # Build the ARG query to retrieve resources query = build_arg_query(resource_group_names, None) __logger.debug("Built ARG Query: %s", query) @@ -25,17 +20,26 @@ def validate_zones(client, cmd, omit_dependent_resources, resource_group_names): resources = execute_arg_query(client, query, None, 0, None, None, False, None) # Run validation on the retrieved resources - validation_results = validate_resources(resources, omit_dependent_resources) + validation_results = validate_resources(cmd, resources, omit_dependent_resources) return validation_results -def validate_resources(resources, omit_dependent_resources=False): +def validate_resources(cmd, resources, omit_dependent_resources=False): resource_results = [] if resources['count'] == 0: errMsg = ("No resources found, validation could not be run.") __logger.error(errMsg) + # Get the location data we'll use to validate the resources + try: + location_data_helper = LocationDataHelper(cmd) + location_data_helper.fetch_location_data() + except Exception as e: # pylint: disable=broad-except + __logger.debug("An error occurred when fetching location data: %s", e) + __logger.warning("An error occurred when fetching location data. \ + Please manually validate if your region supports zones.") + # Loop through the resources and validate each one for resource in resources['data']: resourceProvider = resource['type'].split('/')[0] @@ -43,18 +47,24 @@ def validate_resources(resources, omit_dependent_resources=False): zrStatus = None # If the region does not have zones, we need to look no further - regionHasZones = LocationDataHelper.region_has_zones(region) - if(regionHasZones is False): + # If we were unable to fetch location data before, this will return None and it will fall into the else logic + regionHasZones = location_data_helper.region_has_zones(region) + if not regionHasZones: zrStatus = ZoneRedundancyValidationResult.NoZonesInRegion else: - validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + validator = getResourceTypeValidator(resourceProvider) if validator is None: zrStatus = ZoneRedundancyValidationResult.Unknown - else: + else: try: zrStatus = validator.validate(resource) - except Exception as e: - __logger.warning(f"An error occurred when validating {resource.get('name', '')}: {e}\nPlease check the resource manually.") + except KeyError as e: + __logger.warning("KeyError when validating %s: %s\n \ + Please check the resource manually.", resource.get('name', ''), e) + zrStatus = ZoneRedundancyValidationResult.Unknown + except Exception as e: # pylint: disable=broad-except + __logger.warning("An error occurred when validating %s: %s\n \ + Please check the resource manually.", resource.get('name', ''), e) zrStatus = ZoneRedundancyValidationResult.Unknown if zrStatus is not ZoneRedundancyValidationResult.Dependent or not omit_dependent_resources: @@ -62,7 +72,7 @@ def validate_resources(resources, omit_dependent_resources=False): resource_result['name'] = resource['name'] resource_result['location'] = resource['location'] resource_result['resourceGroup'] = resource['resourceGroup'] - resource_result['resourceType'] = resource['type'] + resource_result['resourceType'] = resource['type'] resource_result['zoneRedundant'] = ZoneRedundancyValidationResult.to_string(zrStatus) resource_results.append(resource_result) diff --git a/src/zones/setup.py b/src/zones/setup.py index 90fd0c5c902..15a164e38c5 100644 --- a/src/zones/setup.py +++ b/src/zones/setup.py @@ -53,4 +53,4 @@ packages=find_packages(), install_requires=DEPENDENCIES, package_data={'azext_zones': ['azext_metadata.json']}, -) \ No newline at end of file +) From e36cb8176cbc7c53893033b183f0d3e7d7dd8c10 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Mon, 28 Apr 2025 13:22:11 +0200 Subject: [PATCH 18/40] fix whitespace issues --- .../microsoft_apimanagement.py | 16 ++++---- .../resource_type_validators/microsoft_app.py | 19 ++++++---- .../microsoft_automation.py | 9 +++-- .../microsoft_cache.py | 14 ++++--- .../resource_type_validators/microsoft_cdn.py | 9 +++-- .../microsoft_chaos.py | 9 +++-- .../microsoft_compute.py | 26 ++++++++----- .../microsoft_containerinstance.py | 10 +++-- .../microsoft_containerregistry.py | 15 +++++--- .../microsoft_containerservice.py | 11 ++++-- .../microsoft_dbforpostgresql.py | 17 +++++---- .../microsoft_documentdb.py | 16 +++++--- .../microsoft_eventgrid.py | 11 ++++-- .../microsoft_eventhub.py | 11 +++--- .../microsoft_hdinsight.py | 8 ++-- .../microsoft_insights.py | 8 ++-- .../microsoft_keyvault.py | 12 +++--- .../microsoft_kusto.py | 15 +++++--- .../microsoft_loadtestservice.py | 8 ++-- .../microsoft_machinelearningservices.py | 12 +++--- .../microsoft_managedidentity.py | 8 ++-- .../microsoft_maps.py | 8 ++-- .../microsoft_network.py | 38 ++++++++++++------- .../microsoft_operationalinsights.py | 16 +++++--- .../microsoft_recoveryservices.py | 16 +++++--- .../microsoft_search.py | 17 +++++---- .../microsoft_servicebus.py | 10 +++-- .../microsoft_signalrservice.py | 15 +++++--- .../resource_type_validators/microsoft_sql.py | 19 ++++++---- .../microsoft_storage.py | 18 +++++---- .../resource_type_validators/microsoft_web.py | 17 +++++---- 31 files changed, 262 insertions(+), 176 deletions(-) diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py index 8b670090d33..0f58c959fc9 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py @@ -4,26 +4,28 @@ @register_resource_type('microsoft.apimanagement') class microsoft_apimanagement: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_apimanagement") - _logger.debug("Validating Microsoft.apimanagement resource type: %s", resourceSubType) + _logger = get_logger("microsoft_apimanagement") + _logger.debug( + "Validating Microsoft.apimanagement resource type: %s", + resourceSubType) match resourceSubType: case 'gateways': # ZR state of the gateway is defined on the service level return ZoneRedundancyValidationResult.Dependent - + case 'service': # API Management instances are zone redundant if they are premium and have more than one zone # https://learn.microsoft.com/azure/api-management/high-availability#availability-zones zones = resource.get('zones') or [] return ZoneRedundancyValidationResult.Yes if len(zones) > 1 \ - and resource['sku']['name'] == 'Premium' \ - else ZoneRedundancyValidationResult.No + and resource['sku']['name'] == 'Premium' \ + else ZoneRedundancyValidationResult.No - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_app.py b/src/zones/azext_zones/resource_type_validators/microsoft_app.py index 847aa08ee80..b0fe02768a6 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_app.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_app.py @@ -4,24 +4,27 @@ @register_resource_type('microsoft.app') class microsoft_app: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_app") - _logger.debug("Validating Microsoft.app resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_app") + _logger.debug( + "Validating Microsoft.app resource type: %s", + resourceSubType) + match resourceSubType: case 'containerapps': - # Container apps are zone redundant if they are hosted on a zone redundant managedEnvironment + # Container apps are zone redundant if they are hosted on a + # zone redundant managedEnvironment return ZoneRedundancyValidationResult.Dependent - + case 'managedenvironments': # Managed Environments are zone redundant if the zoneRedundant property is set to true # https://learn.microsoft.com/azure/reliability/reliability-azure-container-apps#availability-zone-support - return ZoneRedundancyValidationResult.Yes if resource['properties'].get('zoneRedundant', {}) == True \ - else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if resource['properties'].get( + 'zoneRedundant', {}) else ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_automation.py b/src/zones/azext_zones/resource_type_validators/microsoft_automation.py index 7c108eee494..a1d993c2838 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_automation.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_automation.py @@ -4,15 +4,16 @@ @register_resource_type('microsoft.automation') class microsoft_automation: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_automation") - _logger.debug("Validating Microsoft.automation resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_automation") + _logger.debug( + "Validating Microsoft.automation resource type: %s", + resourceSubType) # Automation accounts are zone redundant by default # https://learn.microsoft.com/azure/automation/automation-availability-zones diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py index b1a7ab74e7f..a494781e513 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py @@ -4,15 +4,17 @@ @register_resource_type('microsoft.cache') class microsoft_cache: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_cache") - _logger.debug("Validating Microsoft.cache resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_cache") + _logger.debug( + "Validating Microsoft.cache resource type: %s", + resourceSubType) + match resourceSubType: case 'redis': # Redis caches are zone redundant if they are premium SKU and have more than one zone set @@ -25,5 +27,5 @@ def validate(resource): zones = resource.get('zones') or [] return ZoneRedundancyValidationResult.Yes if len(zones) > 1 \ else ZoneRedundancyValidationResult.No - - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py b/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py index dd0d87f4097..5f724a513c9 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py @@ -4,15 +4,16 @@ @register_resource_type('microsoft.cdn') class microsoft_cdn: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_cdn") - _logger.debug("Validating Microsoft.cdn resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_cdn") + _logger.debug( + "Validating Microsoft.cdn resource type: %s", + resourceSubType) # Cdn profiles are a global service and are zone redundant by default return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py b/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py index 6ff4dcbaf61..dd5176b8376 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py @@ -4,15 +4,16 @@ @register_resource_type('microsoft.chaos') class microsoft_chaos: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_chaos") - _logger.debug("Validating Microsoft.chaos resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_chaos") + _logger.debug( + "Validating Microsoft.chaos resource type: %s", + resourceSubType) # chaos profiles are always zone redundant # https://learn.microsoft.com/azure/reliability/reliability-chaos-studio#availability-zone-support diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py index 461bc967e19..8cee5cb9b72 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py @@ -4,32 +4,38 @@ @register_resource_type('microsoft.compute') class microsoft_compute: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_compute") - _logger.debug("Validating Microsoft.Compute resource type: %s", resourceSubType) + _logger = get_logger("microsoft_compute") + _logger.debug( + "Validating Microsoft.Compute resource type: %s", + resourceSubType) match resourceSubType: case 'disks': zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len(zones) > 1 else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if len( + zones) > 1 else ZoneRedundancyValidationResult.No case 'virtualmachinescalesets': # VMSS is ZR if deployed to more than one zone zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len(zones) > 1 else ZoneRedundancyValidationResult.No - + return ZoneRedundancyValidationResult.Yes if len( + zones) > 1 else ZoneRedundancyValidationResult.No + case 'virtualmachines': # VM is ZR if deployed to more than one zone zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len(zones) > 1 else ZoneRedundancyValidationResult.No - + return ZoneRedundancyValidationResult.Yes if len( + zones) > 1 else ZoneRedundancyValidationResult.No + case 'virtualmachines/extensions': - # VM extensions are zone redundant if the VM they are attached to is zone redundant + # VM extensions are zone redundant if the VM they are attached + # to is zone redundant return ZoneRedundancyValidationResult.Dependent - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py index bd0575c91a1..22b8c55a4eb 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py @@ -4,15 +4,17 @@ @register_resource_type('microsoft.containerinstance') class microsoft_containerinstance: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_containerinstance") - _logger.debug("Validating Microsoft.containerinstance resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_containerinstance") + _logger.debug( + "Validating Microsoft.containerinstance resource type: %s", + resourceSubType) + match resourceSubType: case 'containerGroups': # Container groups of container instances are zonal resources, so they are never zone redundant diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py index d871b701640..36caa36e238 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py @@ -4,22 +4,25 @@ @register_resource_type('microsoft.containerregistry') class microsoft_containerregistry: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_containerregistry") - _logger.debug("Validating Microsoft.containerregistry resource type: %s", resourceSubType) + _logger = get_logger("microsoft_containerregistry") + _logger.debug( + "Validating Microsoft.containerregistry resource type: %s", + resourceSubType) match resourceSubType: case 'registries': # Container registries are zone redundant if the setting enabled # https://learn.microsoft.com/azure/container-registry/zone-redundancy - return ZoneRedundancyValidationResult.Yes if resource['properties']['zoneRedundancy'] == 'Enabled' else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if resource['properties'][ + 'zoneRedundancy'] == 'Enabled' else ZoneRedundancyValidationResult.No case 'registries/replications': return ZoneRedundancyValidationResult.Dependent - - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py index d83ee1d3895..54e208ceef3 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py @@ -4,21 +4,24 @@ @register_resource_type('microsoft.containerservice') class microsoft_containerservice: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_containerservice") - _logger.debug("Validating Microsoft.containerservice resource type: %s", resourceSubType) + _logger = get_logger("microsoft_containerservice") + _logger.debug( + "Validating Microsoft.containerservice resource type: %s", + resourceSubType) match resourceSubType: case 'managedclusters': # AKS clusters are zone redundant if the node pools are spread across multiple zones # Zone Redundancy on AKS involves a lot of configuration steps, testing is required beyond this script. # https://learn.microsoft.com/azure/aks/availability-zones-overview - poolZones = resource['properties']['agentPoolProfiles'][0].get('availabilityZones') or [] + poolZones = resource['properties']['agentPoolProfiles'][0].get( + 'availabilityZones') or [] poolZoneCount = len(poolZones) return ZoneRedundancyValidationResult.Yes if poolZoneCount > 1 else ZoneRedundancyValidationResult.No diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py index 7a7199c74da..5d768b4dc23 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py @@ -4,21 +4,22 @@ @register_resource_type('microsoft.dbforpostgresql') class microsoft_dbforpostgresql: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_dbforpostgresql") - _logger.debug("Validating Microsoft.dbforpostgresql resource type: %s", resourceSubType) + _logger = get_logger("microsoft_dbforpostgresql") + _logger.debug( + "Validating Microsoft.dbforpostgresql resource type: %s", + resourceSubType) match resourceSubType: - case 'flexibleservers': + case 'flexibleservers': return ( - ZoneRedundancyValidationResult.Yes - if resource['properties'].get('highAvailability', {}).get('mode',{}) == 'ZoneRedundant' - else ZoneRedundancyValidationResult.No - ) + ZoneRedundancyValidationResult.Yes if resource['properties'].get( + 'highAvailability', {}).get( + 'mode', {}) == 'ZoneRedundant' else ZoneRedundancyValidationResult.No) return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py index d4098775e5f..dc2db3c2e72 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py @@ -4,19 +4,23 @@ @register_resource_type('microsoft.documentdb') class microsoft_documentdb: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_documentdb") - _logger.debug("Validating Microsoft.documentdb resource type: %s", resourceSubType) + _logger = get_logger("microsoft_documentdb") + _logger.debug( + "Validating Microsoft.documentdb resource type: %s", + resourceSubType) match resourceSubType: case 'databaseaccounts': # https://learn.microsoft.com/en-us/azure/reliability/reliability-cosmos-db-nosql - # CosmosDB databases are zone redundant if then have the setting enabled on the region - return ZoneRedundancyValidationResult.Yes if resource['properties']['locations'][0]['isZoneRedundant'] else ZoneRedundancyValidationResult.No + # CosmosDB databases are zone redundant if then have the + # setting enabled on the region + return ZoneRedundancyValidationResult.Yes if resource['properties'][ + 'locations'][0]['isZoneRedundant'] else ZoneRedundancyValidationResult.No - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py b/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py index b9e8600f9cc..dea3f6b20fe 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py @@ -4,15 +4,18 @@ @register_resource_type('microsoft.eventgrid') class microsoft_eventgrid: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_eventgrid") - _logger.debug("Validating Microsoft.eventgrid resource type: %s", resourceSubType) + _logger = get_logger("microsoft_eventgrid") + _logger.debug( + "Validating Microsoft.eventgrid resource type: %s", + resourceSubType) # EventGrid resources are zone redundant by default - # https://learn.microsoft.com/azure/reliability/reliability-event-grid#availability-zone-support return ZoneRedundancyValidationResult.Always + # https://learn.microsoft.com/azure/reliability/reliability-event-grid#availability-zone-support + # return ZoneRedundancyValidationResult.Always return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py b/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py index 4d4401e1c02..523b6a02325 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py @@ -4,16 +4,17 @@ @register_resource_type('microsoft.eventhub') class microsoft_eventhub: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_eventhub") - _logger.debug("Validating Microsoft.eventhub resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_eventhub") + _logger.debug( + "Validating Microsoft.eventhub resource type: %s", + resourceSubType) - # If you create an Event Hubs namespace in a region that supports availability zones, zone redundancy is automatically enabled. + # If you create an Event Hubs namespace in a region that supports availability zones, zone redundancy is automatically enabled. # https://learn.microsoft.com/azure/reliability/reliability-event-hubs#availability-zone-support return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py b/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py index 6dcdb4f2f89..1d44bf2f941 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py @@ -4,14 +4,16 @@ @register_resource_type('microsoft.hdinsight') class microsoft_hdinsight: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_hdinsight") - _logger.debug("Validating Microsoft.hdinsight resource type: %s", resourceSubType) + _logger = get_logger("microsoft_hdinsight") + _logger.debug( + "Validating Microsoft.hdinsight resource type: %s", + resourceSubType) # HDInsight clusters are zonal resources. They exist in a single zone. return ZoneRedundancyValidationResult.Never diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_insights.py b/src/zones/azext_zones/resource_type_validators/microsoft_insights.py index 83b6191e6b5..e2bd0fd57c3 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_insights.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_insights.py @@ -4,14 +4,16 @@ @register_resource_type('microsoft.insights') class microsoft_insights: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_insights") - _logger.debug("Validating Microsoft.insights resource type: %s", resourceSubType) + _logger = get_logger("microsoft_insights") + _logger.debug( + "Validating Microsoft.insights resource type: %s", + resourceSubType) # insights resources are zone redundant by default return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py b/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py index 863624efe17..7047c13b13c 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py @@ -4,19 +4,21 @@ @register_resource_type('microsoft.keyvault') class microsoft_keyvault: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_keyvault") - _logger.debug("Validating Microsoft.keyvault resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_keyvault") + _logger.debug( + "Validating Microsoft.keyvault resource type: %s", + resourceSubType) + match resourceSubType: case 'vaults': # Key vaults are zone redundant by default # https://learn.microsoft.com/azure/key-vault/general/disaster-recovery-guidance#failover-across-regions return ZoneRedundancyValidationResult.Always - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py index 8043c738821..7aaa185d788 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py @@ -4,21 +4,24 @@ @register_resource_type('microsoft.kusto') class microsoft_kusto: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_kusto") - _logger.debug("Validating Microsoft.kusto resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_kusto") + _logger.debug( + "Validating Microsoft.kusto resource type: %s", + resourceSubType) + match resourceSubType: case 'clusters': # AKS clusters are zone redundant if the node pools are spread across multiple zones # Zone Redundancy on AKS involves a lot of configuration steps, testing is required beyond this script. # https://learn.microsoft.com/azure/aks/availability-zones-overview - zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len(zones) > 1 else ZoneRedundancyValidationResult.No + zones = resource.get('zones') or [] + return ZoneRedundancyValidationResult.Yes if len( + zones) > 1 else ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py index 78525ce6f81..cb028004cd2 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py @@ -4,14 +4,16 @@ @register_resource_type('microsoft.loadtestservice') class microsoft_loadtestservice: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_loadtestservice") - _logger.debug("Validating Microsoft.loadtestservice resource type: %s", resourceSubType) + _logger = get_logger("microsoft_loadtestservice") + _logger.debug( + "Validating Microsoft.loadtestservice resource type: %s", + resourceSubType) # loadtestservice resources are never zone redundant return ZoneRedundancyValidationResult.Never diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py b/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py index 0b94c1645ed..7eebe6bfbaa 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py @@ -4,17 +4,19 @@ @register_resource_type('microsoft.machinelearningservices') class microsoft_machinelearningservices: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_machinelearningservices") - _logger.debug("Validating Microsoft.machinelearningservices resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_machinelearningservices") + _logger.debug( + "Validating Microsoft.machinelearningservices resource type: %s", + resourceSubType) + match resourceSubType: case 'workspaces': return ZoneRedundancyValidationResult.Never - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py b/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py index 125e66fa945..0fbc5f4af85 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py @@ -4,14 +4,16 @@ @register_resource_type('microsoft.managedidentity') class microsoft_managedidentity: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_managedidentity") - _logger.debug("Validating Microsoft.managedidentity resource type: %s", resourceSubType) + _logger = get_logger("microsoft_managedidentity") + _logger.debug( + "Validating Microsoft.managedidentity resource type: %s", + resourceSubType) # managedidentity resources are zone redundant by default return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_maps.py b/src/zones/azext_zones/resource_type_validators/microsoft_maps.py index e97f5459d2c..9207c1329a4 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_maps.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_maps.py @@ -4,14 +4,16 @@ @register_resource_type('microsoft.maps') class microsoft_maps: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_maps") - _logger.debug("Validating Microsoft.maps resource type: %s", resourceSubType) + _logger = get_logger("microsoft_maps") + _logger.debug( + "Validating Microsoft.maps resource type: %s", + resourceSubType) # maps resources are zone redundant by default return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_network.py b/src/zones/azext_zones/resource_type_validators/microsoft_network.py index a645d357afb..181d63a6135 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_network.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_network.py @@ -11,19 +11,24 @@ def validate(resource): resourceSubType = resourceType[resourceType.index('/') + 1:] _logger = get_logger("microsoft_network") - _logger.debug("Validating Microsoft.Network resource type: %s", resourceSubType) + _logger.debug( + "Validating Microsoft.Network resource type: %s", + resourceSubType) match resourceSubType: case 'applicationgateways': zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len(zones) > 1 else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if len( + zones) > 1 else ZoneRedundancyValidationResult.No case 'azurefirewalls': zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len(zones) > 1 and resource['sku']['capacity'] > 1 else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if len( + zones) > 1 and resource['sku']['capacity'] > 1 else ZoneRedundancyValidationResult.No case 'connections': - # Network connections depend on the configuration of the Virtual Network Gateway + # Network connections depend on the configuration of the + # Virtual Network Gateway return ZoneRedundancyValidationResult.Dependent case 'dnszones': @@ -35,16 +40,20 @@ def validate(resource): return ZoneRedundancyValidationResult.Always case 'loadbalancers': - frontend_ip_configs = resource['properties'].get('frontendIPConfigurations') or [] + frontend_ip_configs = resource['properties'].get( + 'frontendIPConfigurations') or [] zones = frontend_ip_configs[0].get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len(zones) > 1 else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if len( + zones) > 1 else ZoneRedundancyValidationResult.No case 'localnetworkgateways': - # Local network gateways depend on the configuration of the VPN Gateway + # Local network gateways depend on the configuration of the VPN + # Gateway return ZoneRedundancyValidationResult.Dependent case 'networkinterfaces': - # Network interfaces are in the zone of the virtual machines they are attached to + # Network interfaces are in the zone of the virtual machines + # they are attached to return ZoneRedundancyValidationResult.Dependent case 'networksecuritygroups': @@ -55,10 +64,10 @@ def validate(resource): return ZoneRedundancyValidationResult.Always case 'privatednszones': - # Private DNS zones are zone redundant by default + # Private DNS zones are zone redundant by default # https://learn.microsoft.com/azure/dns/private-dns-resiliency return ZoneRedundancyValidationResult.Always - + case 'privatednszones/virtualnetworklinks': return ZoneRedundancyValidationResult.Always @@ -67,7 +76,8 @@ def validate(resource): case 'publicipaddresses': zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] in ['Standard'] and len(zones) > 1 else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] in [ + 'Standard'] and len(zones) > 1 else ZoneRedundancyValidationResult.No case 'virtualnetworks': # Virtual networks span all availability zones in a region. @@ -76,7 +86,7 @@ def validate(resource): case 'virtualnetworkgateways': sku = resource['properties']['sku']['name'] - return ZoneRedundancyValidationResult.Yes if sku.endswith('AZ') else ZoneRedundancyValidationResult.No - + return ZoneRedundancyValidationResult.Yes if sku.endswith( + 'AZ') else ZoneRedundancyValidationResult.No - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py b/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py index 6eb516ce45b..469f5980107 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py @@ -4,19 +4,23 @@ @register_resource_type('microsoft.operationalinsights') class microsoft_operationalinsights: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_operationalinsights") - _logger.debug("Validating Microsoft.operationalinsights resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_operationalinsights") + _logger.debug( + "Validating Microsoft.operationalinsights resource type: %s", + resourceSubType) + match resourceSubType: case 'workspaces': # Operational Insights workspaces are zone redundant by default, - # Note: Operational Insights workspaces are zone redundant by default only in some regions. Check https://learn.microsoft.com/azure/azure-monitor/logs/availability-zones. + # Note: Operational Insights workspaces are zone redundant by + # default only in some regions. Check + # https://learn.microsoft.com/azure/azure-monitor/logs/availability-zones. return ZoneRedundancyValidationResult.Always - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py b/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py index 1b646cba87d..dc361655c5b 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py @@ -4,19 +4,23 @@ @register_resource_type('microsoft.recoveryservices') class microsoft_recoveryservices: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_recoveryservices") - _logger.debug("Validating Microsoft.recoveryservices resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_recoveryservices") + _logger.debug( + "Validating Microsoft.recoveryservices resource type: %s", + resourceSubType) + match resourceSubType: case 'vaults': # https://learn.microsoft.com/azure/reliability/reliability-backup#availability-zone-support - # Recovery Services vaults are zone redundant if the storage redundancy was set to ZoneRedundant - return ZoneRedundancyValidationResult.Yes if resource['properties']['redundancySettings']['standardTierStorageRedundancy'] == 'ZoneRedundant' else ZoneRedundancyValidationResult.No + # Recovery Services vaults are zone redundant if the storage + # redundancy was set to ZoneRedundant + return ZoneRedundancyValidationResult.Yes if resource['properties']['redundancySettings'][ + 'standardTierStorageRedundancy'] == 'ZoneRedundant' else ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_search.py b/src/zones/azext_zones/resource_type_validators/microsoft_search.py index 3577da1f76a..3b20417faf9 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_search.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_search.py @@ -4,21 +4,24 @@ @register_resource_type('microsoft.search') class microsoft_search: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_search") - _logger.debug("Validating Microsoft.search resource type: %s", resourceSubType) + _logger = get_logger("microsoft_search") + _logger.debug( + "Validating Microsoft.search resource type: %s", + resourceSubType) match resourceSubType: case 'searchservices': - # Standard or higher tiers in supported regions are zone redundant if the replica count is greater than 1. - # https://learn.microsoft.com/azure/search/search-reliability#availability-zone-support + # Standard or higher tiers in supported regions are zone redundant if the replica count is greater than 1. + # https://learn.microsoft.com/azure/search/search-reliability#availability-zone-support sku = resource['sku']['name'] or '' replicaCount = resource['properties'].get('replicaCount', 0) - return ZoneRedundancyValidationResult.Yes if sku not in ['Free', 'Basic'] and replicaCount > 1 else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if sku not in [ + 'Free', 'Basic'] and replicaCount > 1 else ZoneRedundancyValidationResult.No - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py b/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py index 7e9dd1e0271..1fe3ba68d74 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py @@ -4,15 +4,17 @@ @register_resource_type('microsoft.servicebus') class microsoft_servicebus: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_servicebus") - _logger.debug("Validating Microsoft.servicebus resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_servicebus") + _logger.debug( + "Validating Microsoft.servicebus resource type: %s", + resourceSubType) + match resourceSubType: case 'namespaces': # servicebus namespaces are always zone redundant diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py index 07e6d30f653..8266a1478bd 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py @@ -4,19 +4,22 @@ @register_resource_type('microsoft.signalrservice') class microsoft_signalrservice: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_signalrservice") - _logger.debug("Validating Microsoft.signalrservice resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_signalrservice") + _logger.debug( + "Validating Microsoft.signalrservice resource type: %s", + resourceSubType) + match resourceSubType: case 'signalr': # SignalR is zone redundant by default on premium tiers # https://learn.microsoft.com/azure/azure-signalr/availability-zones - return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] == 'Premium' else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if resource['sku'][ + 'name'] == 'Premium' else ZoneRedundancyValidationResult.No - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py index 2d4b7fa893f..4622507f9a0 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py @@ -4,23 +4,26 @@ @register_resource_type('microsoft.sql') class microsoft_sql: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_sql") - _logger.debug("Validating Microsoft.sql resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_sql") + _logger.debug( + "Validating Microsoft.sql resource type: %s", + resourceSubType) + match resourceSubType: case 'servers/databases': # https://learn.microsoft.com/azure/azure-sql/database/high-availability-sla-local-zone-redundancy#high-availability-through-zone-redundancy - return ZoneRedundancyValidationResult.Yes if resource['properties']['zoneRedundant'] == True else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if resource['properties'][ + 'zoneRedundant'] else ZoneRedundancyValidationResult.No case 'servers': - # Zone Redundancy for SQL is set at the database level, see above + # Zone Redundancy for SQL is set at the database level, see + # above return ZoneRedundancyValidationResult.Dependent - - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py index 916bbc10ca1..18dd2f85402 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py @@ -4,18 +4,22 @@ @register_resource_type('microsoft.storage') class microsoft_storage: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_storage") - _logger.debug("Validating Microsoft.Storage resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_storage") + _logger.debug( + "Validating Microsoft.Storage resource type: %s", + resourceSubType) + match resourceSubType: case 'storageaccounts': - # Storage accounts are zone redundant if they are in the ZRS SKU - return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] == 'Standard_ZRS' else ZoneRedundancyValidationResult.No + # Storage accounts are zone redundant if they are in the ZRS + # SKU + return ZoneRedundancyValidationResult.Yes if resource['sku'][ + 'name'] == 'Standard_ZRS' else ZoneRedundancyValidationResult.No - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_web.py b/src/zones/azext_zones/resource_type_validators/microsoft_web.py index 42e4df4023b..349564c15f1 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_web.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_web.py @@ -4,15 +4,17 @@ @register_resource_type('microsoft.web') class microsoft_web: - + @staticmethod def validate(resource): resourceType = resource['type'] resourceSubType = resourceType[resourceType.index('/') + 1:] - _logger = get_logger("microsoft_web") - _logger.debug("Validating Microsoft.web resource type: %s", resourceSubType) - + _logger = get_logger("microsoft_web") + _logger.debug( + "Validating Microsoft.web resource type: %s", + resourceSubType) + match resourceSubType: case 'serverfarms': # App Service Plans are zone redundant if they have zone redundancy enabled and have more than one instance @@ -20,9 +22,10 @@ def validate(resource): zrEnabled = resource['properties'].get('zoneRedundant', False) instanceCount = resource['sku'].get('capacity', 0) return ZoneRedundancyValidationResult.Yes if zrEnabled and instanceCount > 1 else ZoneRedundancyValidationResult.No - + case 'sites': - # Web Apps are zone redundant if they are hosted on a zone redundant App Service Plan + # Web Apps are zone redundant if they are hosted on a zone + # redundant App Service Plan return ZoneRedundancyValidationResult.Dependent - return ZoneRedundancyValidationResult.Unknown \ No newline at end of file + return ZoneRedundancyValidationResult.Unknown From c2697be24b052f90540fe3da3310c327c7049e84 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Mon, 28 Apr 2025 17:28:01 +0200 Subject: [PATCH 19/40] new types and bugfixes --- .../microsoft_authorization.py | 19 ++++++++ .../microsoft_botservice.py | 31 ++++++++++++ .../microsoft_dashboard.py | 26 ++++++++++ .../microsoft_documentdb.py | 15 +++++- .../microsoft_network.py | 34 ++++++++----- .../latest/test_microsoft_apimanagement.py | 4 +- .../tests/latest/test_microsoft_app.py | 4 +- .../tests/latest/test_microsoft_cache.py | 4 +- .../tests/latest/test_microsoft_compute.py | 4 +- .../test_microsoft_containerregistry.py | 4 +- .../latest/test_microsoft_containerservice.py | 4 +- .../tests/latest/test_microsoft_dashboard.py | 48 +++++++++++++++++++ .../latest/test_microsoft_dbforpostgresql.py | 4 +- .../tests/latest/test_microsoft_documentdb.py | 4 +- .../tests/latest/test_microsoft_kusto.py | 4 +- .../tests/latest/test_microsoft_network.py | 6 +-- .../latest/test_microsoft_recoveryservices.py | 4 +- .../tests/latest/test_microsoft_search.py | 4 +- .../latest/test_microsoft_signalrservice.py | 4 +- .../tests/latest/test_microsoft_sql.py | 4 +- .../tests/latest/test_microsoft_storage.py | 4 +- .../tests/latest/test_microsoft_web.py | 4 +- 22 files changed, 192 insertions(+), 47 deletions(-) create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_authorization.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_botservice.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_dashboard.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_dashboard.py diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_authorization.py b/src/zones/azext_zones/resource_type_validators/microsoft_authorization.py new file mode 100644 index 00000000000..1a3a2aa84ab --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_authorization.py @@ -0,0 +1,19 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.authorization') +class microsoft_authorization: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_authorization") + _logger.debug( + "Validating Microsoft.authorization resource type: %s", + resourceSubType) + + # authorization resources are always zone redundant + return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_botservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_botservice.py new file mode 100644 index 00000000000..f670909aec5 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_botservice.py @@ -0,0 +1,31 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.botservice') +class microsoft_botservice: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_botservice") + _logger.debug( + "Validating Microsoft.botservice resource type: %s", + resourceSubType) + + match resourceSubType: + case 'botservices': + # Bot services are ZR only in west europe and + # only if they are configured as a regional (not global) bot. + if resource['location'] == 'westeurope': + # https://learn.microsoft.com/azure/reliability/reliability-bot + _logger.warning( + "Your bot service resource in westeurope may be zone redundant, \ + but only if it's configured as a regional (not global) bot. Please check manually.") + else: + # Bot services cannot be ZR in any other region + return ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_dashboard.py b/src/zones/azext_zones/resource_type_validators/microsoft_dashboard.py new file mode 100644 index 00000000000..fd1d38bc6b1 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_dashboard.py @@ -0,0 +1,26 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.dashboard') +class microsoft_dashboard: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_dashboard") + _logger.debug( + "Validating Microsoft.dashboard resource type: %s", + resourceSubType) + + match resourceSubType: + case 'grafana': + zr = resource.get('properties', {}).get('zoneRedundancy', '') + if zr == 'Enabled': + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py index dc2db3c2e72..1a48e37b4a3 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py @@ -20,7 +20,18 @@ def validate(resource): # https://learn.microsoft.com/en-us/azure/reliability/reliability-cosmos-db-nosql # CosmosDB databases are zone redundant if then have the # setting enabled on the region - return ZoneRedundancyValidationResult.Yes if resource['properties'][ - 'locations'][0]['isZoneRedundant'] else ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes if \ + resource['properties']['locations'][0]['isZoneRedundant'] \ + else ZoneRedundancyValidationResult.No + + case 'mongoClusters': + # https://learn.microsoft.com/azure/reliability/reliability-cosmos-mongodb#availability-zone-support + highAvailability = \ + resource['properties'].get('highAvailability', '') + if highAvailability.get( + 'targetMode', '') == 'ZoneRedundantPreferred': + return ZoneRedundancyValidationResult.Yes + else: + ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_network.py b/src/zones/azext_zones/resource_type_validators/microsoft_network.py index 181d63a6135..600a650982d 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_network.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_network.py @@ -18,13 +18,17 @@ def validate(resource): match resourceSubType: case 'applicationgateways': zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len( - zones) > 1 else ZoneRedundancyValidationResult.No + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No case 'azurefirewalls': zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len( - zones) > 1 and resource['sku']['capacity'] > 1 else ZoneRedundancyValidationResult.No + if len(zones) > 1 and resource['sku']['capacity'] > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No case 'connections': # Network connections depend on the configuration of the @@ -40,11 +44,13 @@ def validate(resource): return ZoneRedundancyValidationResult.Always case 'loadbalancers': - frontend_ip_configs = resource['properties'].get( - 'frontendIPConfigurations') or [] + frontend_ip_configs = resource['properties'] \ + .get('frontendIPConfigurations') or [] zones = frontend_ip_configs[0].get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len( - zones) > 1 else ZoneRedundancyValidationResult.No + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No case 'localnetworkgateways': # Local network gateways depend on the configuration of the VPN @@ -76,8 +82,10 @@ def validate(resource): case 'publicipaddresses': zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if resource['sku']['name'] in [ - 'Standard'] and len(zones) > 1 else ZoneRedundancyValidationResult.No + if resource['sku']['name'] in ['Standard'] and len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No case 'virtualnetworks': # Virtual networks span all availability zones in a region. @@ -86,7 +94,9 @@ def validate(resource): case 'virtualnetworkgateways': sku = resource['properties']['sku']['name'] - return ZoneRedundancyValidationResult.Yes if sku.endswith( - 'AZ') else ZoneRedundancyValidationResult.No + if sku.endswith('AZ'): + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_apimanagement.py b/src/zones/azext_zones/tests/latest/test_microsoft_apimanagement.py index c74a81eaa3f..0c74eb7e18e 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_apimanagement.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_apimanagement.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_apimanagement(ScenarioTest): @@ -43,7 +43,7 @@ class test_microsoft_apimanagement(ScenarioTest): def setUpClass(cls): super(test_microsoft_apimanagement, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_app.py b/src/zones/azext_zones/tests/latest/test_microsoft_app.py index 69bb761dba1..d6353142e6f 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_app.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_app.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_app(ScenarioTest): @@ -36,7 +36,7 @@ class test_microsoft_app(ScenarioTest): def setUpClass(cls): super(test_microsoft_app, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_cache.py b/src/zones/azext_zones/tests/latest/test_microsoft_cache.py index 6935778e9ce..a1fdf170d8a 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_cache.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_cache.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_cache(ScenarioTest): @@ -34,7 +34,7 @@ class test_microsoft_cache(ScenarioTest): def setUpClass(cls): super(test_microsoft_cache, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_compute.py b/src/zones/azext_zones/tests/latest/test_microsoft_compute.py index 539dc8a3e7c..f472ca720c3 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_compute.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_compute.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_app(ScenarioTest): @@ -66,7 +66,7 @@ class test_microsoft_app(ScenarioTest): def setUpClass(cls): super(test_microsoft_app, cls).setUpClass() resourceProvider = cls.resource_disk_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_disk_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_containerregistry.py b/src/zones/azext_zones/tests/latest/test_microsoft_containerregistry.py index be350a1c017..b6f2995777f 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_containerregistry.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_containerregistry.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_containerregistry(ScenarioTest): @@ -35,7 +35,7 @@ class test_microsoft_containerregistry(ScenarioTest): def setUpClass(cls): super(test_microsoft_containerregistry, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_containerservice.py b/src/zones/azext_zones/tests/latest/test_microsoft_containerservice.py index f9c2f8e25d6..597a36bcc13 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_containerservice.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_containerservice.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_containerservice(ScenarioTest): @@ -44,7 +44,7 @@ class test_microsoft_containerservice(ScenarioTest): def setUpClass(cls): super(test_microsoft_containerservice, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_dashboard.py b/src/zones/azext_zones/tests/latest/test_microsoft_dashboard.py new file mode 100644 index 00000000000..c8f689d6475 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_dashboard.py @@ -0,0 +1,48 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult + + +class test_microsoft_dashboard(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.dashboard/grafana", + "properties": { + "zoneRedundancy": 'Enabled' + } + } + + resource_nonzr = \ + { + "type": "microsoft.dashboard/grafana", + "properties": { + "zoneRedundancy": 'Disabled' + } + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_dashboard, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = getResourceTypeValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_dbforpostgresql.py b/src/zones/azext_zones/tests/latest/test_microsoft_dbforpostgresql.py index 6a63bbf0498..ebf0ddfbef0 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_dbforpostgresql.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_dbforpostgresql.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_dbforpostgresql(ScenarioTest): @@ -39,7 +39,7 @@ class test_microsoft_dbforpostgresql(ScenarioTest): def setUpClass(cls): super(test_microsoft_dbforpostgresql, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_documentdb.py b/src/zones/azext_zones/tests/latest/test_microsoft_documentdb.py index 886c47538c0..88521131a55 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_documentdb.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_documentdb.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_documentdb(ScenarioTest): @@ -42,7 +42,7 @@ class test_microsoft_documentdb(ScenarioTest): def setUpClass(cls): super(test_microsoft_documentdb, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_kusto.py b/src/zones/azext_zones/tests/latest/test_microsoft_kusto.py index 191b8d2aa78..80bd6980e10 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_kusto.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_kusto.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_kusto(ScenarioTest): @@ -34,7 +34,7 @@ class test_microsoft_kusto(ScenarioTest): def setUpClass(cls): super(test_microsoft_kusto, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_network.py b/src/zones/azext_zones/tests/latest/test_microsoft_network.py index ee87e61ae98..2d4021ad9cb 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_network.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_network.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_network(ScenarioTest): @@ -125,7 +125,7 @@ class test_microsoft_network(ScenarioTest): def setUpClass(cls): super(test_microsoft_network, cls).setUpClass() resourceProvider = cls.resource_applicationgateways_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_applicationgateways_zr(self): @@ -163,7 +163,7 @@ def test_publicipaddresses_zr(self): zrResult = self.validator.validate(self.resource_publicipaddresses_zr) self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) - def test_publicipaddresses_onzr(self): + def test_publicipaddresses_nzr(self): # Test for non-zone redundancy scenario zrResult = self.validator.validate(self.resource_publicipaddresses_nonzr) self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_recoveryservices.py b/src/zones/azext_zones/tests/latest/test_microsoft_recoveryservices.py index 97f1e7dbb44..8d1d2b3741f 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_recoveryservices.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_recoveryservices.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_recoveryservices(ScenarioTest): @@ -38,7 +38,7 @@ class test_microsoft_recoveryservices(ScenarioTest): def setUpClass(cls): super(test_microsoft_recoveryservices, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_search.py b/src/zones/azext_zones/tests/latest/test_microsoft_search.py index 5f6db71824b..0a06600b19f 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_search.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_search.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_search(ScenarioTest): @@ -40,7 +40,7 @@ class test_microsoft_search(ScenarioTest): def setUpClass(cls): super(test_microsoft_search, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_signalrservice.py b/src/zones/azext_zones/tests/latest/test_microsoft_signalrservice.py index d903601a2b2..99a0712d2c6 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_signalrservice.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_signalrservice.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_signalrservice(ScenarioTest): @@ -36,7 +36,7 @@ class test_microsoft_signalrservice(ScenarioTest): def setUpClass(cls): super(test_microsoft_signalrservice, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_sql.py b/src/zones/azext_zones/tests/latest/test_microsoft_sql.py index e2c225c30fc..f24c1946bf5 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_sql.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_sql.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_sql(ScenarioTest): @@ -34,7 +34,7 @@ class test_microsoft_sql(ScenarioTest): def setUpClass(cls): super(test_microsoft_sql, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_storage.py b/src/zones/azext_zones/tests/latest/test_microsoft_storage.py index 2d9ae74394f..ae8f1ddb1a3 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_storage.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_storage.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_storage(ScenarioTest): @@ -34,7 +34,7 @@ class test_microsoft_storage(ScenarioTest): def setUpClass(cls): super(test_microsoft_storage, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_web.py b/src/zones/azext_zones/tests/latest/test_microsoft_web.py index 8b18e692975..cc59ced625e 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_web.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_web.py @@ -7,7 +7,7 @@ import unittest from azure.cli.testsdk import (ScenarioTest) -from ..._resourceTypeValidation import ResourceTypeValidatorFactory, ZoneRedundancyValidationResult +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult class test_microsoft_web(ScenarioTest): @@ -41,7 +41,7 @@ class test_microsoft_web(ScenarioTest): def setUpClass(cls): super(test_microsoft_web, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] - cls.validator = ResourceTypeValidatorFactory.getValidator(resourceProvider) + cls.validator = getResourceTypeValidator(resourceProvider) def test_zr(self): From 70dbfd47d7db23ad9e8b54807d3a5be4b859f52f Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Tue, 29 Apr 2025 13:33:19 +0200 Subject: [PATCH 20/40] syntax improvements --- src/zones/README.md | 10 +++++++++- .../microsoft_apimanagement.py | 7 ++++--- .../resource_type_validators/microsoft_app.py | 6 ++++-- .../microsoft_cache.py | 12 ++++++++---- .../microsoft_compute.py | 18 ++++++++++++------ .../microsoft_containerinstance.py | 2 +- .../microsoft_containerregistry.py | 6 ++++-- .../microsoft_dbforpostgresql.py | 10 ++++++---- .../microsoft_documentdb.py | 7 ++++--- .../microsoft_kusto.py | 6 ++++-- .../microsoft_search.py | 6 ++++-- .../microsoft_signalrservice.py | 6 ++++-- .../resource_type_validators/microsoft_sql.py | 6 ++++-- .../microsoft_storage.py | 6 ++++-- .../resource_type_validators/microsoft_web.py | 5 ++++- 15 files changed, 76 insertions(+), 37 deletions(-) diff --git a/src/zones/README.md b/src/zones/README.md index efc6bd2224f..70ec204ccf5 100644 --- a/src/zones/README.md +++ b/src/zones/README.md @@ -25,6 +25,10 @@ The _zones_ CLI extension can help with the first two steps. By running this aga The third step can be validated using Chaos Engineering practices. On Azure, look into Chaos Studio to get started with that. +Suggested use for this extension: +- Manually run this against the production subscription or resource group(s) to validate that all resources have zone redundanct enabled +- Run this as part of your CI/CD pipelines, validating zone redundancy of the resources after deployment in the (pre-)production environment. Consider failing the pipeline if any of the resource results contains _No_ as the result. Note that _no_ only occurs in cases where zone redundancy was not enabled, but could be if the resource was configured differently. + ## USAGE Validate all resources in current scope to which you have read access: @@ -53,6 +57,10 @@ az zones validate --omit-dependent-resources ## Important Notes +- The extension still has missing resource types. These are shown as _Unknown_ in the results. It is essential that you validate zone redundancy of these resources yourself, since your whole application is only zone redundant is all resources are zone redundant. + - The _zones_ CLI extension can only help with resources you can view, i.e. for which you have read access. You must ensure that all relevant resources are indeed listed in the results. -- While this extension is a useful tool in validating zone redundancy on resources, you are still responsible for reviewing the [Reliability Guides](https://learn.microsoft.com/azure/reliability/overview-reliability-guidance) for all the services you use in your applications, as these may contain important information regarding operation in high availability scenarios. \ No newline at end of file +- While this extension is a useful tool in validating zone redundancy on resources, you are still responsible for reviewing the [Reliability Guides](https://learn.microsoft.com/azure/reliability/overview-reliability-guidance) for all the services you use in your applications, as these may contain important information regarding operation in high availability scenarios. + +- Zonal services are considered to be Zone Redundant if they are deployed to at least 2 zones. \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py index 0f58c959fc9..4b72a2435f6 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py @@ -24,8 +24,9 @@ def validate(resource): # API Management instances are zone redundant if they are premium and have more than one zone # https://learn.microsoft.com/azure/api-management/high-availability#availability-zones zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len(zones) > 1 \ - and resource['sku']['name'] == 'Premium' \ - else ZoneRedundancyValidationResult.No + if len(zones) > 1 and resource['sku']['name'] == 'Premium': + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_app.py b/src/zones/azext_zones/resource_type_validators/microsoft_app.py index b0fe02768a6..5dbe118b2b5 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_app.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_app.py @@ -24,7 +24,9 @@ def validate(resource): case 'managedenvironments': # Managed Environments are zone redundant if the zoneRedundant property is set to true # https://learn.microsoft.com/azure/reliability/reliability-azure-container-apps#availability-zone-support - return ZoneRedundancyValidationResult.Yes if resource['properties'].get( - 'zoneRedundant', {}) else ZoneRedundancyValidationResult.No + if resource['properties'].get('zoneRedundant', {}) is True: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py index a494781e513..04c6fe934b8 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py @@ -20,12 +20,16 @@ def validate(resource): # Redis caches are zone redundant if they are premium SKU and have more than one zone set # https://learn.microsoft.com/azure/azure-cache-for-redis/cache-high-availability#zone-redundancy zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len(zones) > 1 \ - else ZoneRedundancyValidationResult.No + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No case 'redisenterprise': zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len(zones) > 1 \ - else ZoneRedundancyValidationResult.No + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py index 8cee5cb9b72..77697b6fd68 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py @@ -18,20 +18,26 @@ def validate(resource): match resourceSubType: case 'disks': zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len( - zones) > 1 else ZoneRedundancyValidationResult.No + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No case 'virtualmachinescalesets': # VMSS is ZR if deployed to more than one zone zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len( - zones) > 1 else ZoneRedundancyValidationResult.No + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No case 'virtualmachines': # VM is ZR if deployed to more than one zone zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len( - zones) > 1 else ZoneRedundancyValidationResult.No + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No case 'virtualmachines/extensions': # VM extensions are zone redundant if the VM they are attached diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py index 22b8c55a4eb..0fddaf97933 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py @@ -16,7 +16,7 @@ def validate(resource): resourceSubType) match resourceSubType: - case 'containerGroups': + case 'containergroups': # Container groups of container instances are zonal resources, so they are never zone redundant # https://learn.microsoft.com/azure/reliability/reliability-containers#availability-zone-support return ZoneRedundancyValidationResult.Never diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py index 36caa36e238..f930b989a7e 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py @@ -19,8 +19,10 @@ def validate(resource): case 'registries': # Container registries are zone redundant if the setting enabled # https://learn.microsoft.com/azure/container-registry/zone-redundancy - return ZoneRedundancyValidationResult.Yes if resource['properties'][ - 'zoneRedundancy'] == 'Enabled' else ZoneRedundancyValidationResult.No + if resource['properties']['zoneRedundancy'] == 'Enabled': + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No case 'registries/replications': return ZoneRedundancyValidationResult.Dependent diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py index 5d768b4dc23..b79aaa5e2df 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py @@ -17,9 +17,11 @@ def validate(resource): match resourceSubType: case 'flexibleservers': - return ( - ZoneRedundancyValidationResult.Yes if resource['properties'].get( - 'highAvailability', {}).get( - 'mode', {}) == 'ZoneRedundant' else ZoneRedundancyValidationResult.No) + + if resource['properties'].get('highAvailability', {}).get( + 'mode', {}) == 'ZoneRedundant': + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py index 1a48e37b4a3..5c1611908eb 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py @@ -20,9 +20,10 @@ def validate(resource): # https://learn.microsoft.com/en-us/azure/reliability/reliability-cosmos-db-nosql # CosmosDB databases are zone redundant if then have the # setting enabled on the region - return ZoneRedundancyValidationResult.Yes if \ - resource['properties']['locations'][0]['isZoneRedundant'] \ - else ZoneRedundancyValidationResult.No + if resource['properties']['locations'][0]['isZoneRedundant']: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No case 'mongoClusters': # https://learn.microsoft.com/azure/reliability/reliability-cosmos-mongodb#availability-zone-support diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py index 7aaa185d788..5aa94e104f2 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py @@ -21,7 +21,9 @@ def validate(resource): # Zone Redundancy on AKS involves a lot of configuration steps, testing is required beyond this script. # https://learn.microsoft.com/azure/aks/availability-zones-overview zones = resource.get('zones') or [] - return ZoneRedundancyValidationResult.Yes if len( - zones) > 1 else ZoneRedundancyValidationResult.No + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_search.py b/src/zones/azext_zones/resource_type_validators/microsoft_search.py index 3b20417faf9..9ac38e5013f 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_search.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_search.py @@ -21,7 +21,9 @@ def validate(resource): # https://learn.microsoft.com/azure/search/search-reliability#availability-zone-support sku = resource['sku']['name'] or '' replicaCount = resource['properties'].get('replicaCount', 0) - return ZoneRedundancyValidationResult.Yes if sku not in [ - 'Free', 'Basic'] and replicaCount > 1 else ZoneRedundancyValidationResult.No + if sku not in ['Free', 'Basic'] and replicaCount > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py index 8266a1478bd..798e3b5a34a 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py @@ -19,7 +19,9 @@ def validate(resource): case 'signalr': # SignalR is zone redundant by default on premium tiers # https://learn.microsoft.com/azure/azure-signalr/availability-zones - return ZoneRedundancyValidationResult.Yes if resource['sku'][ - 'name'] == 'Premium' else ZoneRedundancyValidationResult.No + if resource['sku']['name'] == 'Premium': + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py index 4622507f9a0..d31b4f81963 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py @@ -18,8 +18,10 @@ def validate(resource): match resourceSubType: case 'servers/databases': # https://learn.microsoft.com/azure/azure-sql/database/high-availability-sla-local-zone-redundancy#high-availability-through-zone-redundancy - return ZoneRedundancyValidationResult.Yes if resource['properties'][ - 'zoneRedundant'] else ZoneRedundancyValidationResult.No + if resource['properties']['zoneRedundant']: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No case 'servers': # Zone Redundancy for SQL is set at the database level, see diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py index 18dd2f85402..3a67c594930 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py @@ -19,7 +19,9 @@ def validate(resource): case 'storageaccounts': # Storage accounts are zone redundant if they are in the ZRS # SKU - return ZoneRedundancyValidationResult.Yes if resource['sku'][ - 'name'] == 'Standard_ZRS' else ZoneRedundancyValidationResult.No + if resource['sku']['name'] == 'Standard_ZRS': + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_web.py b/src/zones/azext_zones/resource_type_validators/microsoft_web.py index 349564c15f1..1231ae30be9 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_web.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_web.py @@ -21,7 +21,10 @@ def validate(resource): # https://learn.microsoft.com/azure/reliability/reliability-app-service?pivots=free-shared-basic#availability-zone-support zrEnabled = resource['properties'].get('zoneRedundant', False) instanceCount = resource['sku'].get('capacity', 0) - return ZoneRedundancyValidationResult.Yes if zrEnabled and instanceCount > 1 else ZoneRedundancyValidationResult.No + if zrEnabled and instanceCount > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No case 'sites': # Web Apps are zone redundant if they are hosted on a zone From 19fb1b13fbaad1fe93c450b14b8e4942f7f0fd67 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Tue, 29 Apr 2025 13:36:11 +0200 Subject: [PATCH 21/40] formatting fixes --- .../microsoft_apimanagement.py | 2 +- .../resource_type_validators/microsoft_cache.py | 4 ++-- .../resource_type_validators/microsoft_compute.py | 6 +++--- .../microsoft_containerregistry.py | 2 +- .../microsoft_dbforpostgresql.py | 10 +++++----- .../resource_type_validators/microsoft_documentdb.py | 2 +- .../resource_type_validators/microsoft_kusto.py | 2 +- .../resource_type_validators/microsoft_network.py | 4 ++-- .../resource_type_validators/microsoft_search.py | 2 +- .../microsoft_signalrservice.py | 2 +- .../resource_type_validators/microsoft_sql.py | 2 +- .../resource_type_validators/microsoft_storage.py | 2 +- .../resource_type_validators/microsoft_web.py | 4 ++-- 13 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py index 4b72a2435f6..220f74b0975 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py @@ -25,7 +25,7 @@ def validate(resource): # https://learn.microsoft.com/azure/api-management/high-availability#availability-zones zones = resource.get('zones') or [] if len(zones) > 1 and resource['sku']['name'] == 'Premium': - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py index 04c6fe934b8..54423b156c0 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py @@ -21,14 +21,14 @@ def validate(resource): # https://learn.microsoft.com/azure/azure-cache-for-redis/cache-high-availability#zone-redundancy zones = resource.get('zones') or [] if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No case 'redisenterprise': zones = resource.get('zones') or [] if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py index 77697b6fd68..d387dea7f4d 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py @@ -19,7 +19,7 @@ def validate(resource): case 'disks': zones = resource.get('zones') or [] if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No @@ -27,7 +27,7 @@ def validate(resource): # VMSS is ZR if deployed to more than one zone zones = resource.get('zones') or [] if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No @@ -35,7 +35,7 @@ def validate(resource): # VM is ZR if deployed to more than one zone zones = resource.get('zones') or [] if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py index f930b989a7e..d1510be3786 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py @@ -20,7 +20,7 @@ def validate(resource): # Container registries are zone redundant if the setting enabled # https://learn.microsoft.com/azure/container-registry/zone-redundancy if resource['properties']['zoneRedundancy'] == 'Enabled': - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py index b79aaa5e2df..a0adcadff99 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py @@ -17,11 +17,11 @@ def validate(resource): match resourceSubType: case 'flexibleservers': - - if resource['properties'].get('highAvailability', {}).get( + + if resource['properties'].get('highAvailability', {}).get( 'mode', {}) == 'ZoneRedundant': - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py index 5c1611908eb..b2aabecc59e 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py @@ -21,7 +21,7 @@ def validate(resource): # CosmosDB databases are zone redundant if then have the # setting enabled on the region if resource['properties']['locations'][0]['isZoneRedundant']: - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py index 5aa94e104f2..00d7952fe2d 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py @@ -22,7 +22,7 @@ def validate(resource): # https://learn.microsoft.com/azure/aks/availability-zones-overview zones = resource.get('zones') or [] if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_network.py b/src/zones/azext_zones/resource_type_validators/microsoft_network.py index 600a650982d..75f4e2e76eb 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_network.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_network.py @@ -19,7 +19,7 @@ def validate(resource): case 'applicationgateways': zones = resource.get('zones') or [] if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No @@ -50,7 +50,7 @@ def validate(resource): if len(zones) > 1: return ZoneRedundancyValidationResult.Yes else: - return ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.No case 'localnetworkgateways': # Local network gateways depend on the configuration of the VPN diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_search.py b/src/zones/azext_zones/resource_type_validators/microsoft_search.py index 9ac38e5013f..66f0125cad5 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_search.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_search.py @@ -22,7 +22,7 @@ def validate(resource): sku = resource['sku']['name'] or '' replicaCount = resource['properties'].get('replicaCount', 0) if sku not in ['Free', 'Basic'] and replicaCount > 1: - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py index 798e3b5a34a..2de24e8dcbd 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py @@ -20,7 +20,7 @@ def validate(resource): # SignalR is zone redundant by default on premium tiers # https://learn.microsoft.com/azure/azure-signalr/availability-zones if resource['sku']['name'] == 'Premium': - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py index d31b4f81963..ffd16462d64 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py @@ -19,7 +19,7 @@ def validate(resource): case 'servers/databases': # https://learn.microsoft.com/azure/azure-sql/database/high-availability-sla-local-zone-redundancy#high-availability-through-zone-redundancy if resource['properties']['zoneRedundant']: - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py index 3a67c594930..be154441c8d 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py @@ -20,7 +20,7 @@ def validate(resource): # Storage accounts are zone redundant if they are in the ZRS # SKU if resource['sku']['name'] == 'Standard_ZRS': - return ZoneRedundancyValidationResult.Yes + return ZoneRedundancyValidationResult.Yes else: return ZoneRedundancyValidationResult.No diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_web.py b/src/zones/azext_zones/resource_type_validators/microsoft_web.py index 1231ae30be9..797b0e88687 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_web.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_web.py @@ -22,8 +22,8 @@ def validate(resource): zrEnabled = resource['properties'].get('zoneRedundant', False) instanceCount = resource['sku'].get('capacity', 0) if zrEnabled and instanceCount > 1: - return ZoneRedundancyValidationResult.Yes - else: + return ZoneRedundancyValidationResult.Yes + else: return ZoneRedundancyValidationResult.No case 'sites': From 71005bdb15b19fd3e810f660643044cefb9356d8 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Tue, 29 Apr 2025 14:46:56 +0200 Subject: [PATCH 22/40] added resource types --- .../microsoft_iothub.py | 20 +++++++ .../microsoft_mysql.py | 30 +++++++++++ .../resource_type_validators/microsoft_sql.py | 13 +++++ .../resource_type_validators/microsoft_web.py | 11 ++++ .../tests/latest/test_microsoft_mysql.py | 52 +++++++++++++++++++ 5 files changed, 126 insertions(+) create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_iothub.py create mode 100644 src/zones/azext_zones/resource_type_validators/microsoft_mysql.py create mode 100644 src/zones/azext_zones/tests/latest/test_microsoft_mysql.py diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_iothub.py b/src/zones/azext_zones/resource_type_validators/microsoft_iothub.py new file mode 100644 index 00000000000..f230f09b097 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_iothub.py @@ -0,0 +1,20 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.iothub') +class microsoft_iothub: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_iothub") + _logger.debug( + "Validating Microsoft.iothub resource type: %s", + resourceSubType) + + # Zone Redundancy is enabled by default for IoT Hubs + # https://learn.microsoft.com/azure/iot-hub/iot-hub-ha-dr#availability-zones + return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py b/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py new file mode 100644 index 00000000000..7b85b91f909 --- /dev/null +++ b/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py @@ -0,0 +1,30 @@ +from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from knack.log import get_logger + + +@register_resource_type('microsoft.mysql') +class microsoft_mysql: + + @staticmethod + def validate(resource): + resourceType = resource['type'] + resourceSubType = resourceType[resourceType.index('/') + 1:] + + _logger = get_logger("microsoft_mysql") + _logger.debug( + "Validating Microsoft.mysql resource type: %s", + resourceSubType) + + match resourceSubType: + case 'flexibleservers': + haConfig = resource['properties'].get('highAvailability', {}) \ + .get('mode', {}) + if haConfig == 'ZoneRedundant': + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + case 'servers': + return ZoneRedundancyValidationResult.No + + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py index ffd16462d64..d9b721283e9 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py @@ -28,4 +28,17 @@ def validate(resource): # above return ZoneRedundancyValidationResult.Dependent + case 'managedinstances': + # SQL MI can be zone redundant if this has been enabled on the resource + # https://learn.microsoft.com/azure/azure-sql/managed-instance/high-availability-sla-local-zone-redundancy?view=azuresql#zone-redundant-availability + if resource['properties'].get('zoneRedundant', {}) is True: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + case 'instancepools': + # Instance pools depend on the managed instances within them to + # be zone redundant + return ZoneRedundancyValidationResult.Dependent + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_web.py b/src/zones/azext_zones/resource_type_validators/microsoft_web.py index 797b0e88687..f0ff3551d1c 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_web.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_web.py @@ -31,4 +31,15 @@ def validate(resource): # redundant App Service Plan return ZoneRedundancyValidationResult.Dependent + case 'hostingenvironments': + zrStatus = resource['properties'].get('zoneRedundant', False) + if zrStatus: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + case 'staticsites': + # Static Web Apps are always zone redundant + return ZoneRedundancyValidationResult.Always + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_mysql.py b/src/zones/azext_zones/tests/latest/test_microsoft_mysql.py new file mode 100644 index 00000000000..b12ce9e87b4 --- /dev/null +++ b/src/zones/azext_zones/tests/latest/test_microsoft_mysql.py @@ -0,0 +1,52 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import (ScenarioTest) +from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult + + +class test_microsoft_mysql(ScenarioTest): + + resource_zr = \ + { + "type": "microsoft.mysql/flexibleservers", + "properties": { + "highAvailability": { + "mode": "ZoneRedundant" + } + } + } + + resource_nonzr = \ + { + "type": "microsoft.mysql/flexibleservers", + "properties": { + "highAvailability": { + "mode": "Disabled" + } + } + } + + validator = None + + @classmethod + def setUpClass(cls): + super(test_microsoft_mysql, cls).setUpClass() + resourceProvider = cls.resource_zr['type'].split('/')[0] + cls.validator = getResourceTypeValidator(resourceProvider) + + + def test_zr(self): + # Test for zone redundancy scenario + zrResult = self.validator.validate(self.resource_zr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.Yes) + + def test_nonzr(self): + # Test for non-zone redundancy scenario + zrResult = self.validator.validate(self.resource_nonzr) + self.assertEqual(zrResult, ZoneRedundancyValidationResult.No) \ No newline at end of file From 233ab5b7d66767268e4dc2c536e41239a0860338 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Tue, 29 Apr 2025 14:53:52 +0200 Subject: [PATCH 23/40] readme updates --- src/zones/README.md | 5 ++++- .../resource_type_validators/microsoft_network.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/zones/README.md b/src/zones/README.md index 70ec204ccf5..d3961f8f413 100644 --- a/src/zones/README.md +++ b/src/zones/README.md @@ -13,6 +13,9 @@ For each resource, one of the following statuses will be returned: Dependent # Resource is zone redundant if parent or related resource is zone redundant NoZonesInRegion # The region the resource is deployed in does not have Availability Zones + > **Note** + > This extension is in active development. While an effort has been made to include the most common resource types and their zone redundancy configuration, there are still plenty of resource types missing. More will be added in future releases. In the meantime, if you need specific resources added or have found errors, please raise a Github issue. + ## When should you use this? In order to build a fully zone redundant application, you need to satisfy three criteria: @@ -61,6 +64,6 @@ az zones validate --omit-dependent-resources - The _zones_ CLI extension can only help with resources you can view, i.e. for which you have read access. You must ensure that all relevant resources are indeed listed in the results. -- While this extension is a useful tool in validating zone redundancy on resources, you are still responsible for reviewing the [Reliability Guides](https://learn.microsoft.com/azure/reliability/overview-reliability-guidance) for all the services you use in your applications, as these may contain important information regarding operation in high availability scenarios. +- While this extension is a useful tool in validating zone redundancy on resources, you are still responsible for reviewing the [Reliability Guides](https://learn.microsoft.com/azure/reliability/overview-reliability-guidance) for all the services you use in your applications, as these may contain important information regarding operation in high availability scenarios. Ultimately, the product reliability guides are the authoritative source for zone redundancy guidance. - Zonal services are considered to be Zone Redundant if they are deployed to at least 2 zones. \ No newline at end of file diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_network.py b/src/zones/azext_zones/resource_type_validators/microsoft_network.py index 75f4e2e76eb..74512b37afa 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_network.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_network.py @@ -65,7 +65,7 @@ def validate(resource): case 'networksecuritygroups': return ZoneRedundancyValidationResult.Always - case 'networkwatchers': + case 'networkwatchers' | 'networkwatchers/flowlogs' | 'networkwatchers/packetcaptures': # Network watchers are zone redundant by default return ZoneRedundancyValidationResult.Always From 1aad2ddd02994f1c8d2a2c7d22e982c57e8f33bb Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Tue, 29 Apr 2025 14:55:35 +0200 Subject: [PATCH 24/40] fix note box syntax --- src/zones/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zones/README.md b/src/zones/README.md index d3961f8f413..f09e103c052 100644 --- a/src/zones/README.md +++ b/src/zones/README.md @@ -13,7 +13,7 @@ For each resource, one of the following statuses will be returned: Dependent # Resource is zone redundant if parent or related resource is zone redundant NoZonesInRegion # The region the resource is deployed in does not have Availability Zones - > **Note** + > [!NOTE] > This extension is in active development. While an effort has been made to include the most common resource types and their zone redundancy configuration, there are still plenty of resource types missing. More will be added in future releases. In the meantime, if you need specific resources added or have found errors, please raise a Github issue. ## When should you use this? From 7f6dd37e2fc8e6bbd7156a797d75b6f92461bc3a Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Tue, 29 Apr 2025 14:57:39 +0200 Subject: [PATCH 25/40] fix note box syntax --- src/zones/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zones/README.md b/src/zones/README.md index f09e103c052..0a441540b97 100644 --- a/src/zones/README.md +++ b/src/zones/README.md @@ -13,8 +13,8 @@ For each resource, one of the following statuses will be returned: Dependent # Resource is zone redundant if parent or related resource is zone redundant NoZonesInRegion # The region the resource is deployed in does not have Availability Zones - > [!NOTE] - > This extension is in active development. While an effort has been made to include the most common resource types and their zone redundancy configuration, there are still plenty of resource types missing. More will be added in future releases. In the meantime, if you need specific resources added or have found errors, please raise a Github issue. +> [!NOTE] +> This extension is in active development. While an effort has been made to include the most common resource types and their zone redundancy configuration, there are still plenty of resource types missing. More will be added in future releases. In the meantime, if you need specific resources added or have found errors, please raise a Github issue. ## When should you use this? From c8a201a15d4b75553344359f6701188682cce39d Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Tue, 29 Apr 2025 15:04:35 +0200 Subject: [PATCH 26/40] shorten parameter name --- src/zones/azext_zones/_params.py | 2 +- src/zones/azext_zones/custom.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zones/azext_zones/_params.py b/src/zones/azext_zones/_params.py index 9844b86af16..5d7cace29d2 100644 --- a/src/zones/azext_zones/_params.py +++ b/src/zones/azext_zones/_params.py @@ -11,4 +11,4 @@ def load_arguments(self, _): with self.argument_context('zones validate') as c: c.argument('resource_group_names', options_list=['--resource-groups', '-g'], help='Name of the resource groups, comma separated.', required=False) - c.argument('omit_dependent_resources', options_list=['--omit-dependent-resources'], help='Omit dependent resources from validation.', arg_type=get_three_state_flag(), required=False) + c.argument('omit_dependent', options_list=['--omit-dependent'], help='Omit dependent resources from validation.', arg_type=get_three_state_flag(), required=False) diff --git a/src/zones/azext_zones/custom.py b/src/zones/azext_zones/custom.py index dc0a00a5fe6..881c5b6d977 100644 --- a/src/zones/azext_zones/custom.py +++ b/src/zones/azext_zones/custom.py @@ -11,7 +11,7 @@ __logger = get_logger(__name__) -def validate_zones(client, cmd, omit_dependent_resources, resource_group_names): +def validate_zones(client, cmd, omit_dependent, resource_group_names): # Build the ARG query to retrieve resources query = build_arg_query(resource_group_names, None) __logger.debug("Built ARG Query: %s", query) @@ -20,12 +20,12 @@ def validate_zones(client, cmd, omit_dependent_resources, resource_group_names): resources = execute_arg_query(client, query, None, 0, None, None, False, None) # Run validation on the retrieved resources - validation_results = validate_resources(cmd, resources, omit_dependent_resources) + validation_results = validate_resources(cmd, resources, omit_dependent) return validation_results -def validate_resources(cmd, resources, omit_dependent_resources=False): +def validate_resources(cmd, resources, omit_dependent=False): resource_results = [] if resources['count'] == 0: errMsg = ("No resources found, validation could not be run.") @@ -67,7 +67,7 @@ def validate_resources(cmd, resources, omit_dependent_resources=False): Please check the resource manually.", resource.get('name', ''), e) zrStatus = ZoneRedundancyValidationResult.Unknown - if zrStatus is not ZoneRedundancyValidationResult.Dependent or not omit_dependent_resources: + if zrStatus is not ZoneRedundancyValidationResult.Dependent or not omit_dependent: resource_result = {} resource_result['name'] = resource['name'] resource_result['location'] = resource['location'] From 9026850e740a3340dc5bbdfe5f15b744f0b84e48 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Tue, 29 Apr 2025 15:12:28 +0200 Subject: [PATCH 27/40] metadata updates --- src/zones/HISTORY.rst | 4 ++-- src/zones/azext_zones/commands.py | 2 +- src/zones/setup.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zones/HISTORY.rst b/src/zones/HISTORY.rst index 8c34bccfff8..d8ade94ae25 100644 --- a/src/zones/HISTORY.rst +++ b/src/zones/HISTORY.rst @@ -3,6 +3,6 @@ Release History =============== -0.1.0 +1.0.0b1 ++++++ -* Initial release. \ No newline at end of file +* Initial preview release. \ No newline at end of file diff --git a/src/zones/azext_zones/commands.py b/src/zones/azext_zones/commands.py index 69676e84de3..489240074a1 100644 --- a/src/zones/azext_zones/commands.py +++ b/src/zones/azext_zones/commands.py @@ -9,5 +9,5 @@ def load_command_table(self, _): - with self.command_group('zones', client_factory=cf_zones, is_experimental=True) as g: + with self.command_group('zones', client_factory=cf_zones, is_preview=True) as g: g.custom_command('validate', 'validate_zones') diff --git a/src/zones/setup.py b/src/zones/setup.py index 15a164e38c5..1cc9d2f384e 100644 --- a/src/zones/setup.py +++ b/src/zones/setup.py @@ -16,7 +16,7 @@ # TODO: Confirm this is the right version number you want and it matches your # HISTORY.rst entry. -VERSION = '0.1.0' +VERSION = '1.0.0b1' # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers @@ -35,7 +35,7 @@ # TODO: Add any additional SDK dependencies here DEPENDENCIES = [] -with open('README.rst', 'r', encoding='utf-8') as f: +with open('README.md', 'r', encoding='utf-8') as f: README = f.read() with open('HISTORY.rst', 'r', encoding='utf-8') as f: HISTORY = f.read() From 2fb2f691e26e89b025ea4d53c755288b28fdb66e Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Tue, 29 Apr 2025 15:23:02 +0200 Subject: [PATCH 28/40] requested metadata updates --- src/zones/azext_zones/_help.py | 7 +++++++ src/zones/azext_zones/azext_metadata.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/zones/azext_zones/_help.py b/src/zones/azext_zones/_help.py index e9f354f5043..95b17f033f3 100644 --- a/src/zones/azext_zones/_help.py +++ b/src/zones/azext_zones/_help.py @@ -15,4 +15,11 @@ helps['zones validate'] = """ type: command short-summary: Validates zone redundancy status of all resources in the current subscription context for which you have read access. + examples: + - name: Validate zone redundancy status of all resources in the specified resource group + text: |- + az zones validate --resource-groups myProductionRG --omit-dependent + - name: Validate zone redundancy status of all resources in the specified resource group, but omit the dependent/child resources + text: |- + az zones validate --resource-groups myProductionRG --omit-dependent """ diff --git a/src/zones/azext_zones/azext_metadata.json b/src/zones/azext_zones/azext_metadata.json index 0a142586d70..55c81bf3328 100644 --- a/src/zones/azext_zones/azext_metadata.json +++ b/src/zones/azext_zones/azext_metadata.json @@ -1,4 +1,4 @@ { - "azext.isExperimental": true, + "azext.isPreview": true, "azext.minCliCoreVersion": "2.0.67" } \ No newline at end of file From 0d3b99bc0bc4932ca1dfa4400de102c2035dcb91 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Wed, 30 Apr 2025 10:48:36 +0200 Subject: [PATCH 29/40] update incorrect codeowners path --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a121364a454..25a5c07e1e4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -326,4 +326,4 @@ /src/acat @qinqingxu @Sherylueen @yongxin-ms @wh-alice -/src/azext_zones/ @nielsams +/src/zones/ @nielsams From e82a7c256d7ce0a7f16bc308b6fb27445e4610fc Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Tue, 6 May 2025 10:01:17 +0200 Subject: [PATCH 30/40] add missing license headers --- src/zones/azext_zones/_argHelper.py | 5 +++++ src/zones/azext_zones/_clients.py | 5 +++++ src/zones/azext_zones/_locationDataHelper.py | 5 +++++ src/zones/azext_zones/_resourceTypeValidation.py | 4 ++++ .../resource_type_validators/microsoft_apimanagement.py | 5 +++++ .../azext_zones/resource_type_validators/microsoft_app.py | 5 +++++ .../resource_type_validators/microsoft_authorization.py | 5 +++++ .../resource_type_validators/microsoft_automation.py | 5 +++++ .../resource_type_validators/microsoft_botservice.py | 5 +++++ .../azext_zones/resource_type_validators/microsoft_cache.py | 5 +++++ .../azext_zones/resource_type_validators/microsoft_cdn.py | 5 +++++ .../azext_zones/resource_type_validators/microsoft_chaos.py | 5 +++++ .../resource_type_validators/microsoft_compute.py | 5 +++++ .../resource_type_validators/microsoft_containerinstance.py | 5 +++++ .../resource_type_validators/microsoft_containerregistry.py | 5 +++++ .../resource_type_validators/microsoft_containerservice.py | 5 +++++ .../resource_type_validators/microsoft_dashboard.py | 5 +++++ .../resource_type_validators/microsoft_dbforpostgresql.py | 5 +++++ .../resource_type_validators/microsoft_documentdb.py | 5 +++++ .../resource_type_validators/microsoft_eventgrid.py | 5 +++++ .../resource_type_validators/microsoft_eventhub.py | 5 +++++ .../resource_type_validators/microsoft_hdinsight.py | 5 +++++ .../resource_type_validators/microsoft_insights.py | 5 +++++ .../azext_zones/resource_type_validators/microsoft_iothub.py | 5 +++++ .../resource_type_validators/microsoft_keyvault.py | 5 +++++ .../azext_zones/resource_type_validators/microsoft_kusto.py | 5 +++++ .../resource_type_validators/microsoft_loadtestservice.py | 5 +++++ .../microsoft_machinelearningservices.py | 5 +++++ .../resource_type_validators/microsoft_managedidentity.py | 5 +++++ .../azext_zones/resource_type_validators/microsoft_maps.py | 5 +++++ .../azext_zones/resource_type_validators/microsoft_mysql.py | 5 +++++ .../resource_type_validators/microsoft_network.py | 5 +++++ .../microsoft_operationalinsights.py | 5 +++++ .../resource_type_validators/microsoft_recoveryservices.py | 5 +++++ .../azext_zones/resource_type_validators/microsoft_search.py | 5 +++++ .../resource_type_validators/microsoft_servicebus.py | 5 +++++ .../resource_type_validators/microsoft_signalrservice.py | 5 +++++ .../azext_zones/resource_type_validators/microsoft_sql.py | 5 +++++ .../resource_type_validators/microsoft_storage.py | 5 +++++ .../azext_zones/resource_type_validators/microsoft_web.py | 5 +++++ 40 files changed, 199 insertions(+) diff --git a/src/zones/azext_zones/_argHelper.py b/src/zones/azext_zones/_argHelper.py index 569ffa08243..b2726486719 100644 --- a/src/zones/azext_zones/_argHelper.py +++ b/src/zones/azext_zones/_argHelper.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + import json from collections import OrderedDict from knack.util import todict diff --git a/src/zones/azext_zones/_clients.py b/src/zones/azext_zones/_clients.py index efe709801fd..c084aaf820c 100644 --- a/src/zones/azext_zones/_clients.py +++ b/src/zones/azext_zones/_clients.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from azure.cli.core.util import send_raw_request from azure.cli.core.commands.client_factory import get_subscription_id diff --git a/src/zones/azext_zones/_locationDataHelper.py b/src/zones/azext_zones/_locationDataHelper.py index 4fbe2a8c889..040f283204c 100644 --- a/src/zones/azext_zones/_locationDataHelper.py +++ b/src/zones/azext_zones/_locationDataHelper.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from ._clients import MgmtApiClient from knack.log import get_logger diff --git a/src/zones/azext_zones/_resourceTypeValidation.py b/src/zones/azext_zones/_resourceTypeValidation.py index 3cbb0fd003f..1da8408736e 100644 --- a/src/zones/azext_zones/_resourceTypeValidation.py +++ b/src/zones/azext_zones/_resourceTypeValidation.py @@ -1,3 +1,7 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- from abc import ABC, abstractmethod from enum import Enum diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py index 220f74b0975..2e28fde472f 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_app.py b/src/zones/azext_zones/resource_type_validators/microsoft_app.py index 5dbe118b2b5..fff62847194 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_app.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_app.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_authorization.py b/src/zones/azext_zones/resource_type_validators/microsoft_authorization.py index 1a3a2aa84ab..67f2d35f26e 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_authorization.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_authorization.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_automation.py b/src/zones/azext_zones/resource_type_validators/microsoft_automation.py index a1d993c2838..441ccba225f 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_automation.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_automation.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_botservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_botservice.py index f670909aec5..9ce8ecca7a2 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_botservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_botservice.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py index 54423b156c0..53d72ed1385 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py b/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py index 5f724a513c9..b94a6864067 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py b/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py index dd5176b8376..4f6dd1339eb 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py index d387dea7f4d..490c69afaea 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py index 0fddaf97933..87165c6c5fc 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py index d1510be3786..25a976719d9 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py index 54e208ceef3..9968d228cb3 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_dashboard.py b/src/zones/azext_zones/resource_type_validators/microsoft_dashboard.py index fd1d38bc6b1..988e674a7c8 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_dashboard.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_dashboard.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py index a0adcadff99..f8cc74f7960 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py index b2aabecc59e..16bb02cab2a 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py b/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py index dea3f6b20fe..be35bd9fcee 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py b/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py index 523b6a02325..507ff624243 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py b/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py index 1d44bf2f941..d2e04236206 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_insights.py b/src/zones/azext_zones/resource_type_validators/microsoft_insights.py index e2bd0fd57c3..ed7112746f9 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_insights.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_insights.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_iothub.py b/src/zones/azext_zones/resource_type_validators/microsoft_iothub.py index f230f09b097..50b8210ce62 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_iothub.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_iothub.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py b/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py index 7047c13b13c..27ac56e3e91 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py index 00d7952fe2d..dd681d0bed9 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py index cb028004cd2..844afb30ada 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py b/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py index 7eebe6bfbaa..9c6d3ebfb28 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py b/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py index 0fbc5f4af85..00b2bac688b 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_maps.py b/src/zones/azext_zones/resource_type_validators/microsoft_maps.py index 9207c1329a4..50b1e926c47 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_maps.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_maps.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py b/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py index 7b85b91f909..a4e4ce2bf0a 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_network.py b/src/zones/azext_zones/resource_type_validators/microsoft_network.py index 74512b37afa..336d5accb4f 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_network.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_network.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py b/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py index 469f5980107..b0ed6176fd7 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py b/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py index dc361655c5b..21abb458e08 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_search.py b/src/zones/azext_zones/resource_type_validators/microsoft_search.py index 66f0125cad5..3d82cf05da5 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_search.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_search.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py b/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py index 1fe3ba68d74..f9551bd0923 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py index 2de24e8dcbd..bd566191f90 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py index d9b721283e9..e27b7bff403 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py index be154441c8d..99d2b2be091 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_web.py b/src/zones/azext_zones/resource_type_validators/microsoft_web.py index f0ff3551d1c..55f66bc58ac 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_web.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_web.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type from knack.log import get_logger From dc103be74967d7fb00aa12cf8548c80f4c45171e Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Tue, 6 May 2025 10:09:02 +0200 Subject: [PATCH 31/40] add entry to service_name.json --- src/service_name.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/service_name.json b/src/service_name.json index 4904fb10d97..54d2ee812a3 100644 --- a/src/service_name.json +++ b/src/service_name.json @@ -919,6 +919,11 @@ "AzureServiceName": "Microsoft Connected Cache", "URL": "" }, + { + "Command": "az zones", + "AzureServiceName": "", + "URL": "https://learn.microsoft.com/azure/availability-zones/az-overview" + }, { "Command": "az playwright-testing", "AzureServiceName": "Playwright Testing", From 642d41f3b35805d90785b67c56027033b5e79a53 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 8 May 2025 09:49:27 +0200 Subject: [PATCH 32/40] update service_name value --- src/service_name.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service_name.json b/src/service_name.json index 54d2ee812a3..3ec0c3faaf8 100644 --- a/src/service_name.json +++ b/src/service_name.json @@ -921,7 +921,7 @@ }, { "Command": "az zones", - "AzureServiceName": "", + "AzureServiceName": "Availability Zones", "URL": "https://learn.microsoft.com/azure/availability-zones/az-overview" }, { From 02085d3e4e14fb82e222da09faff58d05274b3d4 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 8 May 2025 09:49:56 +0200 Subject: [PATCH 33/40] add init.py files --- src/zones/azext_zones/resource_type_validators/__init__.py | 0 src/zones/azext_zones/vendored_sdks/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/zones/azext_zones/resource_type_validators/__init__.py create mode 100644 src/zones/azext_zones/vendored_sdks/__init__.py diff --git a/src/zones/azext_zones/resource_type_validators/__init__.py b/src/zones/azext_zones/resource_type_validators/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/zones/azext_zones/vendored_sdks/__init__.py b/src/zones/azext_zones/vendored_sdks/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From dd06e74aaadf6bb56b38040e6e4868924994229c Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 8 May 2025 09:50:36 +0200 Subject: [PATCH 34/40] add init.py files --- src/zones/azext_zones/resource_type_validators/__init__.py | 4 ++++ src/zones/azext_zones/vendored_sdks/__init__.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/zones/azext_zones/resource_type_validators/__init__.py b/src/zones/azext_zones/resource_type_validators/__init__.py index e69de29bb2d..34913fb394d 100644 --- a/src/zones/azext_zones/resource_type_validators/__init__.py +++ b/src/zones/azext_zones/resource_type_validators/__init__.py @@ -0,0 +1,4 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/src/zones/azext_zones/vendored_sdks/__init__.py b/src/zones/azext_zones/vendored_sdks/__init__.py index e69de29bb2d..34913fb394d 100644 --- a/src/zones/azext_zones/vendored_sdks/__init__.py +++ b/src/zones/azext_zones/vendored_sdks/__init__.py @@ -0,0 +1,4 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- From d406c6143ffb5ee9e0a5a4193b97a161b088d79b Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 8 May 2025 13:11:44 +0200 Subject: [PATCH 35/40] update module init --- src/zones/azext_zones/resource_type_validators/__init__.py | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 src/zones/azext_zones/resource_type_validators/__init__.py diff --git a/src/zones/azext_zones/resource_type_validators/__init__.py b/src/zones/azext_zones/resource_type_validators/__init__.py deleted file mode 100644 index 34913fb394d..00000000000 --- a/src/zones/azext_zones/resource_type_validators/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- From b28db3b1efa29cda0c8e70b34861701b65fe9eae Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Mon, 12 May 2025 11:04:04 +0200 Subject: [PATCH 36/40] update VERSION parameter --- src/zones/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zones/setup.py b/src/zones/setup.py index 1cc9d2f384e..402e474fd50 100644 --- a/src/zones/setup.py +++ b/src/zones/setup.py @@ -42,7 +42,7 @@ setup( name='zones', - version=0.1, + version=VERSION, description='Microsoft Azure Command-Line Tools Zones Extension', author='Niels Buit', author_email='nielsb@microsoft.com', From 6fd4706646004cad3bd4547919eb6f181f498156 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Tue, 13 May 2025 12:44:09 +0200 Subject: [PATCH 37/40] syntax changes for python39 compliance --- .../microsoft_apimanagement.py | 44 ++-- .../resource_type_validators/microsoft_app.py | 45 ++-- .../microsoft_authorization.py | 16 +- .../microsoft_automation.py | 16 +- .../microsoft_botservice.py | 41 ++-- .../microsoft_cache.py | 51 ++--- .../resource_type_validators/microsoft_cdn.py | 16 +- .../microsoft_chaos.py | 16 +- .../microsoft_compute.py | 77 +++---- .../microsoft_containerinstance.py | 26 +-- .../microsoft_containerregistry.py | 41 ++-- .../microsoft_containerservice.py | 40 ++-- .../microsoft_dashboard.py | 30 +-- .../microsoft_dbforpostgresql.py | 35 +-- .../microsoft_documentdb.py | 55 ++--- .../microsoft_eventgrid.py | 16 +- .../microsoft_eventhub.py | 16 +- .../microsoft_hdinsight.py | 16 +- .../microsoft_insights.py | 16 +- .../microsoft_iothub.py | 16 +- .../microsoft_keyvault.py | 26 +-- .../microsoft_kusto.py | 36 ++-- .../microsoft_loadtestservice.py | 16 +- .../microsoft_machinelearningservices.py | 21 +- .../microsoft_managedidentity.py | 16 +- .../microsoft_maps.py | 16 +- .../microsoft_mysql.py | 40 ++-- .../microsoft_network.py | 204 ++++++++++-------- .../microsoft_operationalinsights.py | 29 +-- .../microsoft_recoveryservices.py | 36 ++-- .../microsoft_search.py | 36 ++-- .../microsoft_servicebus.py | 24 ++- .../microsoft_signalrservice.py | 32 +-- .../resource_type_validators/microsoft_sql.py | 71 +++--- .../microsoft_storage.py | 32 +-- .../resource_type_validators/microsoft_web.py | 73 ++++--- 36 files changed, 715 insertions(+), 622 deletions(-) diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py index 2e28fde472f..d0fcb47b7da 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_apimanagement.py @@ -3,35 +3,37 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.apimanagement') +@register_resource_type("microsoft.apimanagement") class microsoft_apimanagement: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_apimanagement") _logger.debug( - "Validating Microsoft.apimanagement resource type: %s", - resourceSubType) - - match resourceSubType: - case 'gateways': - # ZR state of the gateway is defined on the service level - return ZoneRedundancyValidationResult.Dependent - - case 'service': - # API Management instances are zone redundant if they are premium and have more than one zone - # https://learn.microsoft.com/azure/api-management/high-availability#availability-zones - zones = resource.get('zones') or [] - if len(zones) > 1 and resource['sku']['name'] == 'Premium': - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No + "Validating Microsoft.apimanagement resource type: %s", resourceSubType + ) + + # API Management Gateways + if resourceSubType == "gateways": + # ZR state of the gateway is defined on the service level + return ZoneRedundancyValidationResult.Dependent + + if resourceSubType == "service": + # API Management instances are zone redundant if they are premium and have more than one zone + # https://learn.microsoft.com/azure/api-management/high-availability#availability-zones + zones = resource.get("zones") or [] + if len(zones) > 1 and resource["sku"]["name"] == "Premium": + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_app.py b/src/zones/azext_zones/resource_type_validators/microsoft_app.py index fff62847194..a6f4ef31622 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_app.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_app.py @@ -3,35 +3,36 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.app') +@register_resource_type("microsoft.app") class microsoft_app: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_app") - _logger.debug( - "Validating Microsoft.app resource type: %s", - resourceSubType) - - match resourceSubType: - case 'containerapps': - # Container apps are zone redundant if they are hosted on a - # zone redundant managedEnvironment - return ZoneRedundancyValidationResult.Dependent - - case 'managedenvironments': - # Managed Environments are zone redundant if the zoneRedundant property is set to true - # https://learn.microsoft.com/azure/reliability/reliability-azure-container-apps#availability-zone-support - if resource['properties'].get('zoneRedundant', {}) is True: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No + _logger.debug("Validating Microsoft.app resource type: %s", resourceSubType) + + # Container Apps + if resourceSubType == "containerapps": + # Container apps are zone redundant if they are hosted on a + # zone redundant managedEnvironment + return ZoneRedundancyValidationResult.Dependent + + # Container Apps Environments + if resourceSubType == "managedenvironments": + # Managed Environments are zone redundant if the zoneRedundant property is set to true + # https://learn.microsoft.com/azure/reliability/reliability-azure-container-apps#availability-zone-support + if resource["properties"].get("zoneRedundant", {}) is True: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_authorization.py b/src/zones/azext_zones/resource_type_validators/microsoft_authorization.py index 67f2d35f26e..580523c065b 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_authorization.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_authorization.py @@ -3,22 +3,24 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.authorization') +@register_resource_type("microsoft.authorization") class microsoft_authorization: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_authorization") _logger.debug( - "Validating Microsoft.authorization resource type: %s", - resourceSubType) + "Validating Microsoft.authorization resource type: %s", resourceSubType + ) # authorization resources are always zone redundant return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_automation.py b/src/zones/azext_zones/resource_type_validators/microsoft_automation.py index 441ccba225f..62e9e281ec6 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_automation.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_automation.py @@ -3,22 +3,24 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.automation') +@register_resource_type("microsoft.automation") class microsoft_automation: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_automation") _logger.debug( - "Validating Microsoft.automation resource type: %s", - resourceSubType) + "Validating Microsoft.automation resource type: %s", resourceSubType + ) # Automation accounts are zone redundant by default # https://learn.microsoft.com/azure/automation/automation-availability-zones diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_botservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_botservice.py index 9ce8ecca7a2..edac1eb6f07 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_botservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_botservice.py @@ -3,34 +3,37 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.botservice') +@register_resource_type("microsoft.botservice") class microsoft_botservice: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_botservice") _logger.debug( - "Validating Microsoft.botservice resource type: %s", - resourceSubType) + "Validating Microsoft.botservice resource type: %s", resourceSubType + ) - match resourceSubType: - case 'botservices': - # Bot services are ZR only in west europe and - # only if they are configured as a regional (not global) bot. - if resource['location'] == 'westeurope': - # https://learn.microsoft.com/azure/reliability/reliability-bot - _logger.warning( - "Your bot service resource in westeurope may be zone redundant, \ - but only if it's configured as a regional (not global) bot. Please check manually.") - else: - # Bot services cannot be ZR in any other region - return ZoneRedundancyValidationResult.No + # Bot Services + if resourceSubType == "botservices": + # Bot services are ZR only in west europe and + # only if they are configured as a regional (not global) bot. + if resource["location"] == "westeurope": + # https://learn.microsoft.com/azure/reliability/reliability-bot + _logger.warning( + "Your bot service resource in westeurope may be zone redundant, \ + but only if it's configured as a regional (not global) bot. Please check manually." + ) + else: + # Bot services cannot be ZR in any other region + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py index 53d72ed1385..1e11e4df040 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_cache.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_cache.py @@ -3,38 +3,39 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.cache') +@register_resource_type("microsoft.cache") class microsoft_cache: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_cache") - _logger.debug( - "Validating Microsoft.cache resource type: %s", - resourceSubType) - - match resourceSubType: - case 'redis': - # Redis caches are zone redundant if they are premium SKU and have more than one zone set - # https://learn.microsoft.com/azure/azure-cache-for-redis/cache-high-availability#zone-redundancy - zones = resource.get('zones') or [] - if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'redisenterprise': - zones = resource.get('zones') or [] - if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No + _logger.debug("Validating Microsoft.cache resource type: %s", resourceSubType) + + # Redis + if resourceSubType == "redis": + # Redis caches are zone redundant if they are premium SKU and have more than one zone set + # https://learn.microsoft.com/azure/azure-cache-for-redis/cache-high-availability#zone-redundancy + zones = resource.get("zones") or [] + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # Redis Enterprise + if resourceSubType == "redisenterprise": + zones = resource.get("zones") or [] + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py b/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py index b94a6864067..cf5d35c4960 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_cdn.py @@ -3,22 +3,22 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.cdn') +@register_resource_type("microsoft.cdn") class microsoft_cdn: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_cdn") - _logger.debug( - "Validating Microsoft.cdn resource type: %s", - resourceSubType) + _logger.debug("Validating Microsoft.cdn resource type: %s", resourceSubType) # Cdn profiles are a global service and are zone redundant by default return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py b/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py index 4f6dd1339eb..9bb8c1cb196 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_chaos.py @@ -3,22 +3,22 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.chaos') +@register_resource_type("microsoft.chaos") class microsoft_chaos: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_chaos") - _logger.debug( - "Validating Microsoft.chaos resource type: %s", - resourceSubType) + _logger.debug("Validating Microsoft.chaos resource type: %s", resourceSubType) # chaos profiles are always zone redundant # https://learn.microsoft.com/azure/reliability/reliability-chaos-studio#availability-zone-support diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py index 490c69afaea..c0fbe8423a0 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_compute.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_compute.py @@ -3,50 +3,53 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.compute') +@register_resource_type("microsoft.compute") class microsoft_compute: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_compute") - _logger.debug( - "Validating Microsoft.Compute resource type: %s", - resourceSubType) - - match resourceSubType: - case 'disks': - zones = resource.get('zones') or [] - if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'virtualmachinescalesets': - # VMSS is ZR if deployed to more than one zone - zones = resource.get('zones') or [] - if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'virtualmachines': - # VM is ZR if deployed to more than one zone - zones = resource.get('zones') or [] - if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'virtualmachines/extensions': - # VM extensions are zone redundant if the VM they are attached - # to is zone redundant - return ZoneRedundancyValidationResult.Dependent + _logger.debug("Validating Microsoft.Compute resource type: %s", resourceSubType) + + # Disks + if resourceSubType == "disks": + zones = resource.get("zones") or [] + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # VM ScaleSets + if resourceSubType == "virtualmachinescalesets": + # VMSS is ZR if deployed to more than one zone + zones = resource.get("zones") or [] + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # Virtual Machines + if resourceSubType == "virtualmachines": + # VM is ZR if deployed to more than one zone + zones = resource.get("zones") or [] + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # VM Extensions + if resourceSubType == "virtualmachines/extensions": + # VM extensions are zone redundant if the VM they are attached + # to is zone redundant + return ZoneRedundancyValidationResult.Dependent return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py index 87165c6c5fc..4e6b296d2dc 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerinstance.py @@ -3,27 +3,29 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.containerinstance') +@register_resource_type("microsoft.containerinstance") class microsoft_containerinstance: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_containerinstance") _logger.debug( - "Validating Microsoft.containerinstance resource type: %s", - resourceSubType) + "Validating Microsoft.containerinstance resource type: %s", resourceSubType + ) - match resourceSubType: - case 'containergroups': - # Container groups of container instances are zonal resources, so they are never zone redundant - # https://learn.microsoft.com/azure/reliability/reliability-containers#availability-zone-support - return ZoneRedundancyValidationResult.Never + # Container Instances + if resourceSubType == "containergroups": + # Container groups of container instances are zonal resources, so they are never zone redundant + # https://learn.microsoft.com/azure/reliability/reliability-containers#availability-zone-support + return ZoneRedundancyValidationResult.Never return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py index 25a976719d9..4706fbd884d 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py @@ -3,33 +3,36 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.containerregistry') +@register_resource_type("microsoft.containerregistry") class microsoft_containerregistry: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_containerregistry") _logger.debug( - "Validating Microsoft.containerregistry resource type: %s", - resourceSubType) - - match resourceSubType: - case 'registries': - # Container registries are zone redundant if the setting enabled - # https://learn.microsoft.com/azure/container-registry/zone-redundancy - if resource['properties']['zoneRedundancy'] == 'Enabled': - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'registries/replications': - return ZoneRedundancyValidationResult.Dependent + "Validating Microsoft.containerregistry resource type: %s", resourceSubType + ) + + # Container Registry + if resourceSubType == "registries": + # Container registries are zone redundant if the setting enabled + # https://learn.microsoft.com/azure/container-registry/zone-redundancy + if resource["properties"]["zoneRedundancy"] == "Enabled": + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # Registry Regional Replications + if resourceSubType == "registries/replications": + return ZoneRedundancyValidationResult.Dependent return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py index 9968d228cb3..aba6bda5db7 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerservice.py @@ -3,31 +3,39 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.containerservice') +@register_resource_type("microsoft.containerservice") class microsoft_containerservice: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_containerservice") _logger.debug( - "Validating Microsoft.containerservice resource type: %s", - resourceSubType) + "Validating Microsoft.containerservice resource type: %s", resourceSubType + ) - match resourceSubType: - case 'managedclusters': - # AKS clusters are zone redundant if the node pools are spread across multiple zones - # Zone Redundancy on AKS involves a lot of configuration steps, testing is required beyond this script. - # https://learn.microsoft.com/azure/aks/availability-zones-overview - poolZones = resource['properties']['agentPoolProfiles'][0].get( - 'availabilityZones') or [] - poolZoneCount = len(poolZones) - return ZoneRedundancyValidationResult.Yes if poolZoneCount > 1 else ZoneRedundancyValidationResult.No + # AKS Clusters + if resourceSubType == "managedclusters": + # AKS clusters are zone redundant if the node pools are spread across multiple zones + # Zone Redundancy on AKS involves a lot of configuration steps, testing is required beyond this script. + # https://learn.microsoft.com/azure/aks/availability-zones-overview + poolZones = ( + resource["properties"]["agentPoolProfiles"][0].get("availabilityZones") + or [] + ) + poolZoneCount = len(poolZones) + return ( + ZoneRedundancyValidationResult.Yes + if poolZoneCount > 1 + else ZoneRedundancyValidationResult.No + ) return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_dashboard.py b/src/zones/azext_zones/resource_type_validators/microsoft_dashboard.py index 988e674a7c8..66f6255eeea 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_dashboard.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_dashboard.py @@ -3,29 +3,31 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.dashboard') +@register_resource_type("microsoft.dashboard") class microsoft_dashboard: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_dashboard") _logger.debug( - "Validating Microsoft.dashboard resource type: %s", - resourceSubType) + "Validating Microsoft.dashboard resource type: %s", resourceSubType + ) - match resourceSubType: - case 'grafana': - zr = resource.get('properties', {}).get('zoneRedundancy', '') - if zr == 'Enabled': - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No + # Azure Managed Grafana + if resourceSubType == "grafana": + zr = resource.get("properties", {}).get("zoneRedundancy", "") + if zr == "Enabled": + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py index f8cc74f7960..32f4ac02595 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py @@ -3,30 +3,33 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.dbforpostgresql') +@register_resource_type("microsoft.dbforpostgresql") class microsoft_dbforpostgresql: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_dbforpostgresql") _logger.debug( - "Validating Microsoft.dbforpostgresql resource type: %s", - resourceSubType) - - match resourceSubType: - case 'flexibleservers': - - if resource['properties'].get('highAvailability', {}).get( - 'mode', {}) == 'ZoneRedundant': - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No + "Validating Microsoft.dbforpostgresql resource type: %s", resourceSubType + ) + + # PostgreSQL Flexible Servers + if resourceSubType == "flexibleservers": + if ( + resource["properties"].get("highAvailability", {}).get("mode", {}) + == "ZoneRedundant" + ): + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py index 16bb02cab2a..187c96fa648 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_documentdb.py @@ -3,41 +3,42 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.documentdb') +@register_resource_type("microsoft.documentdb") class microsoft_documentdb: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_documentdb") _logger.debug( - "Validating Microsoft.documentdb resource type: %s", - resourceSubType) - - match resourceSubType: - case 'databaseaccounts': - # https://learn.microsoft.com/en-us/azure/reliability/reliability-cosmos-db-nosql - # CosmosDB databases are zone redundant if then have the - # setting enabled on the region - if resource['properties']['locations'][0]['isZoneRedundant']: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'mongoClusters': - # https://learn.microsoft.com/azure/reliability/reliability-cosmos-mongodb#availability-zone-support - highAvailability = \ - resource['properties'].get('highAvailability', '') - if highAvailability.get( - 'targetMode', '') == 'ZoneRedundantPreferred': - return ZoneRedundancyValidationResult.Yes - else: - ZoneRedundancyValidationResult.No + "Validating Microsoft.documentdb resource type: %s", resourceSubType + ) + + # CosmosDB SQL API Accounts + if resourceSubType == "databaseaccounts": + # https://learn.microsoft.com/en-us/azure/reliability/reliability-cosmos-db-nosql + # CosmosDB databases are zone redundant if then have the + # setting enabled on the region + if resource["properties"]["locations"][0]["isZoneRedundant"]: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # CosmosDB MongoDB API Accounts + if resourceSubType == "mongoClusters": + # https://learn.microsoft.com/azure/reliability/reliability-cosmos-mongodb#availability-zone-support + highAvailability = resource["properties"].get("highAvailability", "") + if highAvailability.get("targetMode", "") == "ZoneRedundantPreferred": + return ZoneRedundancyValidationResult.Yes + else: + ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py b/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py index be35bd9fcee..2328b960624 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_eventgrid.py @@ -3,22 +3,24 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.eventgrid') +@register_resource_type("microsoft.eventgrid") class microsoft_eventgrid: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_eventgrid") _logger.debug( - "Validating Microsoft.eventgrid resource type: %s", - resourceSubType) + "Validating Microsoft.eventgrid resource type: %s", resourceSubType + ) # EventGrid resources are zone redundant by default # https://learn.microsoft.com/azure/reliability/reliability-event-grid#availability-zone-support diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py b/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py index 507ff624243..448ba1a3fb5 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_eventhub.py @@ -3,22 +3,24 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.eventhub') +@register_resource_type("microsoft.eventhub") class microsoft_eventhub: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_eventhub") _logger.debug( - "Validating Microsoft.eventhub resource type: %s", - resourceSubType) + "Validating Microsoft.eventhub resource type: %s", resourceSubType + ) # If you create an Event Hubs namespace in a region that supports availability zones, zone redundancy is automatically enabled. # https://learn.microsoft.com/azure/reliability/reliability-event-hubs#availability-zone-support diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py b/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py index d2e04236206..2692248a4ac 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_hdinsight.py @@ -3,22 +3,24 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.hdinsight') +@register_resource_type("microsoft.hdinsight") class microsoft_hdinsight: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_hdinsight") _logger.debug( - "Validating Microsoft.hdinsight resource type: %s", - resourceSubType) + "Validating Microsoft.hdinsight resource type: %s", resourceSubType + ) # HDInsight clusters are zonal resources. They exist in a single zone. return ZoneRedundancyValidationResult.Never diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_insights.py b/src/zones/azext_zones/resource_type_validators/microsoft_insights.py index ed7112746f9..0c76089dace 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_insights.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_insights.py @@ -3,22 +3,24 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.insights') +@register_resource_type("microsoft.insights") class microsoft_insights: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_insights") _logger.debug( - "Validating Microsoft.insights resource type: %s", - resourceSubType) + "Validating Microsoft.insights resource type: %s", resourceSubType + ) # insights resources are zone redundant by default return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_iothub.py b/src/zones/azext_zones/resource_type_validators/microsoft_iothub.py index 50b8210ce62..241c1513c22 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_iothub.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_iothub.py @@ -3,22 +3,22 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.iothub') +@register_resource_type("microsoft.iothub") class microsoft_iothub: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_iothub") - _logger.debug( - "Validating Microsoft.iothub resource type: %s", - resourceSubType) + _logger.debug("Validating Microsoft.iothub resource type: %s", resourceSubType) # Zone Redundancy is enabled by default for IoT Hubs # https://learn.microsoft.com/azure/iot-hub/iot-hub-ha-dr#availability-zones diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py b/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py index 27ac56e3e91..938bb6358e7 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_keyvault.py @@ -3,27 +3,29 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.keyvault') +@register_resource_type("microsoft.keyvault") class microsoft_keyvault: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_keyvault") _logger.debug( - "Validating Microsoft.keyvault resource type: %s", - resourceSubType) + "Validating Microsoft.keyvault resource type: %s", resourceSubType + ) - match resourceSubType: - case 'vaults': - # Key vaults are zone redundant by default - # https://learn.microsoft.com/azure/key-vault/general/disaster-recovery-guidance#failover-across-regions - return ZoneRedundancyValidationResult.Always + # Key Vaults + if resourceSubType == "vaults": + # Key vaults are zone redundant by default + # https://learn.microsoft.com/azure/key-vault/general/disaster-recovery-guidance#failover-across-regions + return ZoneRedundancyValidationResult.Always return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py index dd681d0bed9..9774672b4cd 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_kusto.py @@ -3,32 +3,32 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.kusto') +@register_resource_type("microsoft.kusto") class microsoft_kusto: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_kusto") - _logger.debug( - "Validating Microsoft.kusto resource type: %s", - resourceSubType) + _logger.debug("Validating Microsoft.kusto resource type: %s", resourceSubType) - match resourceSubType: - case 'clusters': - # AKS clusters are zone redundant if the node pools are spread across multiple zones - # Zone Redundancy on AKS involves a lot of configuration steps, testing is required beyond this script. - # https://learn.microsoft.com/azure/aks/availability-zones-overview - zones = resource.get('zones') or [] - if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No + # Kusto Clusters + if resourceSubType == "clusters": + # AKS clusters are zone redundant if the node pools are spread across multiple zones + # Zone Redundancy on AKS involves a lot of configuration steps, testing is required beyond this script. + # https://learn.microsoft.com/azure/aks/availability-zones-overview + zones = resource.get("zones") or [] + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py index 844afb30ada..64b15779a02 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_loadtestservice.py @@ -3,22 +3,24 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.loadtestservice') +@register_resource_type("microsoft.loadtestservice") class microsoft_loadtestservice: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_loadtestservice") _logger.debug( - "Validating Microsoft.loadtestservice resource type: %s", - resourceSubType) + "Validating Microsoft.loadtestservice resource type: %s", resourceSubType + ) # loadtestservice resources are never zone redundant return ZoneRedundancyValidationResult.Never diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py b/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py index 9c6d3ebfb28..2f29049dbc0 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_machinelearningservices.py @@ -3,25 +3,28 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.machinelearningservices') +@register_resource_type("microsoft.machinelearningservices") class microsoft_machinelearningservices: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_machinelearningservices") _logger.debug( "Validating Microsoft.machinelearningservices resource type: %s", - resourceSubType) + resourceSubType, + ) - match resourceSubType: - case 'workspaces': - return ZoneRedundancyValidationResult.Never + # Azure Machine Learning + if resourceSubType == "workspaces": + return ZoneRedundancyValidationResult.Never return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py b/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py index 00b2bac688b..16e7a8c839c 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_managedidentity.py @@ -3,22 +3,24 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.managedidentity') +@register_resource_type("microsoft.managedidentity") class microsoft_managedidentity: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_managedidentity") _logger.debug( - "Validating Microsoft.managedidentity resource type: %s", - resourceSubType) + "Validating Microsoft.managedidentity resource type: %s", resourceSubType + ) # managedidentity resources are zone redundant by default return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_maps.py b/src/zones/azext_zones/resource_type_validators/microsoft_maps.py index 50b1e926c47..2bc634375e2 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_maps.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_maps.py @@ -3,22 +3,22 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.maps') +@register_resource_type("microsoft.maps") class microsoft_maps: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_maps") - _logger.debug( - "Validating Microsoft.maps resource type: %s", - resourceSubType) + _logger.debug("Validating Microsoft.maps resource type: %s", resourceSubType) # maps resources are zone redundant by default return ZoneRedundancyValidationResult.Always diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py b/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py index a4e4ce2bf0a..92056a28557 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py @@ -3,33 +3,35 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.mysql') +@register_resource_type("microsoft.mysql") class microsoft_mysql: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_mysql") - _logger.debug( - "Validating Microsoft.mysql resource type: %s", - resourceSubType) - - match resourceSubType: - case 'flexibleservers': - haConfig = resource['properties'].get('highAvailability', {}) \ - .get('mode', {}) - if haConfig == 'ZoneRedundant': - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'servers': + _logger.debug("Validating Microsoft.mysql resource type: %s", resourceSubType) + + # Azure Database for MySQL + if resourceSubType == "flexibleservers": + haConfig = ( + resource["properties"].get("highAvailability", {}).get("mode", {}) + ) + if haConfig == "ZoneRedundant": + return ZoneRedundancyValidationResult.Yes + else: return ZoneRedundancyValidationResult.No + # Azure Database for MySQL Single Servers + if resourceSubType == "servers": + return ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_network.py b/src/zones/azext_zones/resource_type_validators/microsoft_network.py index 336d5accb4f..54793d8638a 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_network.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_network.py @@ -3,105 +3,125 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.network') +@register_resource_type("microsoft.network") class microsoft_network: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_network") - _logger.debug( - "Validating Microsoft.Network resource type: %s", - resourceSubType) - - match resourceSubType: - case 'applicationgateways': - zones = resource.get('zones') or [] - if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'azurefirewalls': - zones = resource.get('zones') or [] - if len(zones) > 1 and resource['sku']['capacity'] > 1: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'connections': - # Network connections depend on the configuration of the - # Virtual Network Gateway - return ZoneRedundancyValidationResult.Dependent - - case 'dnszones': - # Azure DNS is a global service, zone redundant by default - return ZoneRedundancyValidationResult.Always - - case 'frontdoors': - # Front Door is a global resources and always zone redundant - return ZoneRedundancyValidationResult.Always - - case 'loadbalancers': - frontend_ip_configs = resource['properties'] \ - .get('frontendIPConfigurations') or [] - zones = frontend_ip_configs[0].get('zones') or [] - if len(zones) > 1: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'localnetworkgateways': - # Local network gateways depend on the configuration of the VPN - # Gateway - return ZoneRedundancyValidationResult.Dependent - - case 'networkinterfaces': - # Network interfaces are in the zone of the virtual machines - # they are attached to - return ZoneRedundancyValidationResult.Dependent - - case 'networksecuritygroups': - return ZoneRedundancyValidationResult.Always - - case 'networkwatchers' | 'networkwatchers/flowlogs' | 'networkwatchers/packetcaptures': - # Network watchers are zone redundant by default - return ZoneRedundancyValidationResult.Always - - case 'privatednszones': - # Private DNS zones are zone redundant by default - # https://learn.microsoft.com/azure/dns/private-dns-resiliency - return ZoneRedundancyValidationResult.Always - - case 'privatednszones/virtualnetworklinks': - return ZoneRedundancyValidationResult.Always - - case 'privateendpoints': - return ZoneRedundancyValidationResult.Always - - case 'publicipaddresses': - zones = resource.get('zones') or [] - if resource['sku']['name'] in ['Standard'] and len(zones) > 1: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'virtualnetworks': - # Virtual networks span all availability zones in a region. - # https://learn.microsoft.com/azure/virtual-network/virtual-networks-overview#virtual-networks-and-availability-zones - return ZoneRedundancyValidationResult.Always - - case 'virtualnetworkgateways': - sku = resource['properties']['sku']['name'] - if sku.endswith('AZ'): - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No + _logger.debug("Validating Microsoft.Network resource type: %s", resourceSubType) + + # Application Gateways + if resourceSubType == "applicationgateways": + zones = resource.get("zones") or [] + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # Azure Firewalls + if resourceSubType == "azurefirewalls": + zones = resource.get("zones") or [] + if len(zones) > 1 and resource["sku"]["capacity"] > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # Network Connections + if resourceSubType == "connections": + # Network connections depend on the configuration of the + # Virtual Network Gateway + return ZoneRedundancyValidationResult.Dependent + + # DNS Zones + if resourceSubType == "dnszones": + # Azure DNS is a global service, zone redundant by default + return ZoneRedundancyValidationResult.Always + + # Front Doors + if resourceSubType == "frontdoors": + # Front Door is a global resources and always zone redundant + return ZoneRedundancyValidationResult.Always + + # Load Balancers + if resourceSubType == "loadbalancers": + frontend_ip_configs = ( + resource["properties"].get("frontendIPConfigurations") or [] + ) + zones = frontend_ip_configs[0].get("zones") or [] + if len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # Local Network Gateways + if resourceSubType == "localnetworkgateways": + # Local network gateways depend on the configuration of the VPN + # Gateway + return ZoneRedundancyValidationResult.Dependent + + # Network Interfaces + if resourceSubType == "networkinterfaces": + # Network interfaces are in the zone of the virtual machines + # they are attached to + return ZoneRedundancyValidationResult.Dependent + + # Network Security Groups + if resourceSubType == "networksecuritygroups": + return ZoneRedundancyValidationResult.Always + + # Network Watchers, flowslogs, packetcaptures + if ( + resourceSubType == "networkwatchers" + or resourceSubType == "networkwatchers/flowlogs" + or resourceSubType == "networkwatchers/packetcaptures" + ): + # Network watchers are zone redundant by default + return ZoneRedundancyValidationResult.Always + + # Private DNS Zones + if resourceSubType == "privatednszones": + # Private DNS zones are zone redundant by default + # https://learn.microsoft.com/azure/dns/private-dns-resiliency + return ZoneRedundancyValidationResult.Always + + # Private DNS Zone Virtual Network Links + if resourceSubType == "privatednszones/virtualnetworklinks": + return ZoneRedundancyValidationResult.Always + + # Private Endpoints + if resourceSubType == "privateendpoints": + return ZoneRedundancyValidationResult.Always + + # Public IP Addresses + if resourceSubType == "publicipaddresses": + zones = resource.get("zones") or [] + if resource["sku"]["name"] in ["Standard"] and len(zones) > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # Virtual Networks + if resourceSubType == "virtualnetworks": + # Virtual networks span all availability zones in a region. + # https://learn.microsoft.com/azure/virtual-network/virtual-networks-overview#virtual-networks-and-availability-zones + return ZoneRedundancyValidationResult.Always + + # Virtual Network Gateways + if resourceSubType == "virtualnetworkgateways": + sku = resource["properties"]["sku"]["name"] + if sku.endswith("AZ"): + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py b/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py index b0ed6176fd7..606c94d44dc 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_operationalinsights.py @@ -3,29 +3,32 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.operationalinsights') +@register_resource_type("microsoft.operationalinsights") class microsoft_operationalinsights: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_operationalinsights") _logger.debug( "Validating Microsoft.operationalinsights resource type: %s", - resourceSubType) + resourceSubType, + ) - match resourceSubType: - case 'workspaces': - # Operational Insights workspaces are zone redundant by default, - # Note: Operational Insights workspaces are zone redundant by - # default only in some regions. Check - # https://learn.microsoft.com/azure/azure-monitor/logs/availability-zones. - return ZoneRedundancyValidationResult.Always + # Operational Insights workspaces + if resourceSubType == "workspaces": + # Operational Insights workspaces are zone redundant by default, + # Note: Operational Insights workspaces are zone redundant by + # default only in some regions. Check + # https://learn.microsoft.com/azure/azure-monitor/logs/availability-zones. + return ZoneRedundancyValidationResult.Always return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py b/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py index 21abb458e08..35fe1a4eae2 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_recoveryservices.py @@ -3,29 +3,37 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.recoveryservices') +@register_resource_type("microsoft.recoveryservices") class microsoft_recoveryservices: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_recoveryservices") _logger.debug( - "Validating Microsoft.recoveryservices resource type: %s", - resourceSubType) + "Validating Microsoft.recoveryservices resource type: %s", resourceSubType + ) - match resourceSubType: - case 'vaults': - # https://learn.microsoft.com/azure/reliability/reliability-backup#availability-zone-support - # Recovery Services vaults are zone redundant if the storage - # redundancy was set to ZoneRedundant - return ZoneRedundancyValidationResult.Yes if resource['properties']['redundancySettings'][ - 'standardTierStorageRedundancy'] == 'ZoneRedundant' else ZoneRedundancyValidationResult.No + # Recovery Services vaults + if resourceSubType == "vaults": + # https://learn.microsoft.com/azure/reliability/reliability-backup#availability-zone-support + # Recovery Services vaults are zone redundant if the storage + # redundancy was set to ZoneRedundant + return ( + ZoneRedundancyValidationResult.Yes + if resource["properties"]["redundancySettings"][ + "standardTierStorageRedundancy" + ] + == "ZoneRedundant" + else ZoneRedundancyValidationResult.No + ) return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_search.py b/src/zones/azext_zones/resource_type_validators/microsoft_search.py index 3d82cf05da5..126cb7b89f4 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_search.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_search.py @@ -3,32 +3,32 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.search') +@register_resource_type("microsoft.search") class microsoft_search: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_search") - _logger.debug( - "Validating Microsoft.search resource type: %s", - resourceSubType) + _logger.debug("Validating Microsoft.search resource type: %s", resourceSubType) - match resourceSubType: - case 'searchservices': - # Standard or higher tiers in supported regions are zone redundant if the replica count is greater than 1. - # https://learn.microsoft.com/azure/search/search-reliability#availability-zone-support - sku = resource['sku']['name'] or '' - replicaCount = resource['properties'].get('replicaCount', 0) - if sku not in ['Free', 'Basic'] and replicaCount > 1: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No + # Search Services + if resourceSubType == "searchservices": + # Standard or higher tiers in supported regions are zone redundant if the replica count is greater than 1. + # https://learn.microsoft.com/azure/search/search-reliability#availability-zone-support + sku = resource["sku"]["name"] or "" + replicaCount = resource["properties"].get("replicaCount", 0) + if sku not in ["Free", "Basic"] and replicaCount > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py b/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py index f9551bd0923..30915e3d40d 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_servicebus.py @@ -3,26 +3,28 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.servicebus') +@register_resource_type("microsoft.servicebus") class microsoft_servicebus: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_servicebus") _logger.debug( - "Validating Microsoft.servicebus resource type: %s", - resourceSubType) + "Validating Microsoft.servicebus resource type: %s", resourceSubType + ) - match resourceSubType: - case 'namespaces': - # servicebus namespaces are always zone redundant - return ZoneRedundancyValidationResult.Always + # Service Bus Namespaces + if resourceSubType == "namespaces": + # servicebus namespaces are always zone redundant + return ZoneRedundancyValidationResult.Always return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py b/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py index bd566191f90..3e319b9ab78 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_signalrservice.py @@ -3,30 +3,32 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.signalrservice') +@register_resource_type("microsoft.signalrservice") class microsoft_signalrservice: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_signalrservice") _logger.debug( - "Validating Microsoft.signalrservice resource type: %s", - resourceSubType) + "Validating Microsoft.signalrservice resource type: %s", resourceSubType + ) - match resourceSubType: - case 'signalr': - # SignalR is zone redundant by default on premium tiers - # https://learn.microsoft.com/azure/azure-signalr/availability-zones - if resource['sku']['name'] == 'Premium': - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No + # SignalR Service + if resourceSubType == "signalr": + # SignalR is zone redundant by default on premium tiers + # https://learn.microsoft.com/azure/azure-signalr/availability-zones + if resource["sku"]["name"] == "Premium": + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py index e27b7bff403..a327228d6a1 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_sql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_sql.py @@ -3,47 +3,50 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.sql') +@register_resource_type("microsoft.sql") class microsoft_sql: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_sql") - _logger.debug( - "Validating Microsoft.sql resource type: %s", - resourceSubType) - - match resourceSubType: - case 'servers/databases': - # https://learn.microsoft.com/azure/azure-sql/database/high-availability-sla-local-zone-redundancy#high-availability-through-zone-redundancy - if resource['properties']['zoneRedundant']: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'servers': - # Zone Redundancy for SQL is set at the database level, see - # above - return ZoneRedundancyValidationResult.Dependent - - case 'managedinstances': - # SQL MI can be zone redundant if this has been enabled on the resource - # https://learn.microsoft.com/azure/azure-sql/managed-instance/high-availability-sla-local-zone-redundancy?view=azuresql#zone-redundant-availability - if resource['properties'].get('zoneRedundant', {}) is True: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'instancepools': - # Instance pools depend on the managed instances within them to - # be zone redundant - return ZoneRedundancyValidationResult.Dependent + _logger.debug("Validating Microsoft.sql resource type: %s", resourceSubType) + + # SQL Databases + if resourceSubType == "servers/databases": + # https://learn.microsoft.com/azure/azure-sql/database/high-availability-sla-local-zone-redundancy#high-availability-through-zone-redundancy + if resource["properties"]["zoneRedundant"]: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # SQL Servers + if resourceSubType == "servers": + # Zone Redundancy for SQL is set at the database level, see + # above + return ZoneRedundancyValidationResult.Dependent + + # SQL Managed Instances + if resourceSubType == "managedinstances": + # SQL MI can be zone redundant if this has been enabled on the resource + # https://learn.microsoft.com/azure/azure-sql/managed-instance/high-availability-sla-local-zone-redundancy?view=azuresql#zone-redundant-availability + if resource["properties"].get("zoneRedundant", {}) is True: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # SQL Managed Instance Pools + if resourceSubType == "instancepools": + # Instance pools depend on the managed instances within them to + # be zone redundant + return ZoneRedundancyValidationResult.Dependent return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py index 99d2b2be091..d40390b21ff 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_storage.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_storage.py @@ -3,30 +3,30 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.storage') +@register_resource_type("microsoft.storage") class microsoft_storage: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_storage") - _logger.debug( - "Validating Microsoft.Storage resource type: %s", - resourceSubType) + _logger.debug("Validating Microsoft.Storage resource type: %s", resourceSubType) - match resourceSubType: - case 'storageaccounts': - # Storage accounts are zone redundant if they are in the ZRS - # SKU - if resource['sku']['name'] == 'Standard_ZRS': - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No + # Storage accounts + if resourceSubType == "storageaccounts": + # Storage accounts are zone redundant if they are in the ZRS + # SKU + if resource["sku"]["name"] == "Standard_ZRS": + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_web.py b/src/zones/azext_zones/resource_type_validators/microsoft_web.py index 55f66bc58ac..c1fed9c949e 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_web.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_web.py @@ -3,48 +3,51 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .._resourceTypeValidation import ZoneRedundancyValidationResult, register_resource_type +from .._resourceTypeValidation import ( + ZoneRedundancyValidationResult, + register_resource_type, +) from knack.log import get_logger -@register_resource_type('microsoft.web') +@register_resource_type("microsoft.web") class microsoft_web: - @staticmethod def validate(resource): - resourceType = resource['type'] - resourceSubType = resourceType[resourceType.index('/') + 1:] + resourceType = resource["type"] + resourceSubType = resourceType[resourceType.index("/") + 1:] _logger = get_logger("microsoft_web") - _logger.debug( - "Validating Microsoft.web resource type: %s", - resourceSubType) - - match resourceSubType: - case 'serverfarms': - # App Service Plans are zone redundant if they have zone redundancy enabled and have more than one instance - # https://learn.microsoft.com/azure/reliability/reliability-app-service?pivots=free-shared-basic#availability-zone-support - zrEnabled = resource['properties'].get('zoneRedundant', False) - instanceCount = resource['sku'].get('capacity', 0) - if zrEnabled and instanceCount > 1: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'sites': - # Web Apps are zone redundant if they are hosted on a zone - # redundant App Service Plan - return ZoneRedundancyValidationResult.Dependent - - case 'hostingenvironments': - zrStatus = resource['properties'].get('zoneRedundant', False) - if zrStatus: - return ZoneRedundancyValidationResult.Yes - else: - return ZoneRedundancyValidationResult.No - - case 'staticsites': - # Static Web Apps are always zone redundant - return ZoneRedundancyValidationResult.Always + _logger.debug("Validating Microsoft.web resource type: %s", resourceSubType) + + # App Service Plans + if resourceSubType == "serverfarms": + # App Service Plans are zone redundant if they have zone redundancy enabled and have more than one instance + # https://learn.microsoft.com/azure/reliability/reliability-app-service?pivots=free-shared-basic#availability-zone-support + zrEnabled = resource["properties"].get("zoneRedundant", False) + instanceCount = resource["sku"].get("capacity", 0) + if zrEnabled and instanceCount > 1: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # App Services + if resourceSubType == "sites": + # Web Apps are zone redundant if they are hosted on a zone + # redundant App Service Plan + return ZoneRedundancyValidationResult.Dependent + + # Static Web Apps Hosting Environments + if resourceSubType == "hostingenvironments": + zrStatus = resource["properties"].get("zoneRedundant", False) + if zrStatus: + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No + + # Static Web Apps + if resourceSubType == "staticsites": + # Static Web Apps are always zone redundant + return ZoneRedundancyValidationResult.Always return ZoneRedundancyValidationResult.Unknown From 971aa493fc97c52cd986ad54bc63397a74cd81e1 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Wed, 14 May 2025 16:54:33 +0200 Subject: [PATCH 38/40] update to resource validators --- .../microsoft_containerregistry.py | 5 ++++- .../{microsoft_mysql.py => microsoft_dbformysql.py} | 4 ++-- .../resource_type_validators/microsoft_dbforpostgresql.py | 6 ++++++ ...st_microsoft_mysql.py => test_microsoft_dbformysql.py} | 8 ++++---- 4 files changed, 16 insertions(+), 7 deletions(-) rename src/zones/azext_zones/resource_type_validators/{microsoft_mysql.py => microsoft_dbformysql.py} (92%) rename src/zones/azext_zones/tests/latest/{test_microsoft_mysql.py => test_microsoft_dbformysql.py} (86%) diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py index 4706fbd884d..0c860e17177 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_containerregistry.py @@ -33,6 +33,9 @@ def validate(resource): # Registry Regional Replications if resourceSubType == "registries/replications": - return ZoneRedundancyValidationResult.Dependent + if resource["properties"]["zoneRedundancy"] == "Enabled": + return ZoneRedundancyValidationResult.Yes + else: + return ZoneRedundancyValidationResult.No return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py b/src/zones/azext_zones/resource_type_validators/microsoft_dbformysql.py similarity index 92% rename from src/zones/azext_zones/resource_type_validators/microsoft_mysql.py rename to src/zones/azext_zones/resource_type_validators/microsoft_dbformysql.py index 92056a28557..b6f071666ff 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_mysql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_dbformysql.py @@ -10,14 +10,14 @@ from knack.log import get_logger -@register_resource_type("microsoft.mysql") +@register_resource_type("microsoft.dbformysql") class microsoft_mysql: @staticmethod def validate(resource): resourceType = resource["type"] resourceSubType = resourceType[resourceType.index("/") + 1:] - _logger = get_logger("microsoft_mysql") + _logger = get_logger("microsoft_dbformysql") _logger.debug("Validating Microsoft.mysql resource type: %s", resourceSubType) # Azure Database for MySQL diff --git a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py index 32f4ac02595..7d7b7d2558c 100644 --- a/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py +++ b/src/zones/azext_zones/resource_type_validators/microsoft_dbforpostgresql.py @@ -32,4 +32,10 @@ def validate(resource): else: return ZoneRedundancyValidationResult.No + # PostgreSQL Single Servers + if resourceSubType == "servers": + # Zone redundancy is not supported for PostgreSQL Single Servers + # https://learn.microsoft.com/azure/reliability/reliability-postgresql-flexible-server + return ZoneRedundancyValidationResult.No + return ZoneRedundancyValidationResult.Unknown diff --git a/src/zones/azext_zones/tests/latest/test_microsoft_mysql.py b/src/zones/azext_zones/tests/latest/test_microsoft_dbformysql.py similarity index 86% rename from src/zones/azext_zones/tests/latest/test_microsoft_mysql.py rename to src/zones/azext_zones/tests/latest/test_microsoft_dbformysql.py index b12ce9e87b4..0263c06c822 100644 --- a/src/zones/azext_zones/tests/latest/test_microsoft_mysql.py +++ b/src/zones/azext_zones/tests/latest/test_microsoft_dbformysql.py @@ -10,11 +10,11 @@ from ..._resourceTypeValidation import getResourceTypeValidator, ZoneRedundancyValidationResult -class test_microsoft_mysql(ScenarioTest): +class test_microsoft_dbformysql(ScenarioTest): resource_zr = \ { - "type": "microsoft.mysql/flexibleservers", + "type": "microsoft.dbformysql/flexibleservers", "properties": { "highAvailability": { "mode": "ZoneRedundant" @@ -24,7 +24,7 @@ class test_microsoft_mysql(ScenarioTest): resource_nonzr = \ { - "type": "microsoft.mysql/flexibleservers", + "type": "microsoft.dbformysql/flexibleservers", "properties": { "highAvailability": { "mode": "Disabled" @@ -36,7 +36,7 @@ class test_microsoft_mysql(ScenarioTest): @classmethod def setUpClass(cls): - super(test_microsoft_mysql, cls).setUpClass() + super(test_microsoft_dbformysql, cls).setUpClass() resourceProvider = cls.resource_zr['type'].split('/')[0] cls.validator = getResourceTypeValidator(resourceProvider) From 0161bb992caa453a10ce514151baf4c745364792 Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 15 May 2025 10:08:07 +0200 Subject: [PATCH 39/40] add support for tag filter --- src/zones/README.md | 6 ++++++ src/zones/azext_zones/_argHelper.py | 27 ++++++++++++++++++++++++--- src/zones/azext_zones/_help.py | 3 +++ src/zones/azext_zones/_params.py | 1 + src/zones/azext_zones/custom.py | 4 ++-- 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/zones/README.md b/src/zones/README.md index 0a441540b97..a8abb2dcbe1 100644 --- a/src/zones/README.md +++ b/src/zones/README.md @@ -58,6 +58,12 @@ Omit 'dependent' resources from the output. These are resources that by themselv az zones validate --omit-dependent-resources ``` +Validate all resources with specific tags. Resources that have ALL specified tags will be returned + +```bash +az zones validate --tags env=prod,criticality=high +``` + ## Important Notes - The extension still has missing resource types. These are shown as _Unknown_ in the results. It is essential that you validate zone redundancy of these resources yourself, since your whole application is only zone redundant is all resources are zone redundant. diff --git a/src/zones/azext_zones/_argHelper.py b/src/zones/azext_zones/_argHelper.py index b2726486719..efea5788058 100644 --- a/src/zones/azext_zones/_argHelper.py +++ b/src/zones/azext_zones/_argHelper.py @@ -20,15 +20,36 @@ __logger = get_logger(__name__) -def build_arg_query(resource_groups, attributes): +def build_arg_query(resource_groups, tags): # type: (list[str], list[str]) -> str query = "Resources" if resource_groups is not None and len(resource_groups) > 0: + # Replace spaces with commas in resource groups, in case someone passed a space-separated list + resource_groups = resource_groups.replace(' ', ',') query += " | where resourceGroup in ({0})".format(','.join(f"'{item}'" for item in resource_groups.split(','))) - if attributes is not None and len(attributes) > 0: - query += " | project {0}".format(', '.join(attributes)) + if tags is not None: + # Replace spaces with commas in tags, in case someone passed a space-separated list + tags = tags.replace(' ', ',') + tagquery = [] + for tag in tags.split(','): + tag = tag.strip() + if not tag: # Skip empty tags + continue + + if '=' in tag: + # Tag with a value (TagA=ValueA) + tag_name, tag_value = tag.split('=', 1) + # Escape single quotes in the value + tag_value = tag_value.replace("'", "''") + tagquery.append(f"tags['{tag_name}'] == '{tag_value}'") + else: + # Tag without a value. We don't support those. + pass + + if tagquery: # Only proceed if tagquery has items + query += " | where " + " and ".join(tagquery) return query diff --git a/src/zones/azext_zones/_help.py b/src/zones/azext_zones/_help.py index 95b17f033f3..82f81a73223 100644 --- a/src/zones/azext_zones/_help.py +++ b/src/zones/azext_zones/_help.py @@ -22,4 +22,7 @@ - name: Validate zone redundancy status of all resources in the specified resource group, but omit the dependent/child resources text: |- az zones validate --resource-groups myProductionRG --omit-dependent + - name: Validate zone redundancy status of all resources that have ALL the specified tags + text: |- + az zones validate --tags env=prod,criticality=high """ diff --git a/src/zones/azext_zones/_params.py b/src/zones/azext_zones/_params.py index 5d7cace29d2..ecbb5c24fb3 100644 --- a/src/zones/azext_zones/_params.py +++ b/src/zones/azext_zones/_params.py @@ -11,4 +11,5 @@ def load_arguments(self, _): with self.argument_context('zones validate') as c: c.argument('resource_group_names', options_list=['--resource-groups', '-g'], help='Name of the resource groups, comma separated.', required=False) + c.argument('tags', options_list=['--tags'], help='Filter resources based on tags.', required=False) c.argument('omit_dependent', options_list=['--omit-dependent'], help='Omit dependent resources from validation.', arg_type=get_three_state_flag(), required=False) diff --git a/src/zones/azext_zones/custom.py b/src/zones/azext_zones/custom.py index 881c5b6d977..8ab4141b39e 100644 --- a/src/zones/azext_zones/custom.py +++ b/src/zones/azext_zones/custom.py @@ -11,9 +11,9 @@ __logger = get_logger(__name__) -def validate_zones(client, cmd, omit_dependent, resource_group_names): +def validate_zones(client, cmd, omit_dependent, resource_group_names, tags): # Build the ARG query to retrieve resources - query = build_arg_query(resource_group_names, None) + query = build_arg_query(resource_group_names, tags) __logger.debug("Built ARG Query: %s", query) # Retrieve the list of resources to validate From ba72e95dff9833b5dc19fc5d2a2c4ef61282006e Mon Sep 17 00:00:00 2001 From: Niels Buit Date: Thu, 15 May 2025 12:02:32 +0200 Subject: [PATCH 40/40] improved input validation --- src/zones/azext_zones/_argHelper.py | 4 ---- src/zones/azext_zones/_validators.py | 32 ++++++++++++++++++++++++++++ src/zones/azext_zones/commands.py | 4 ++-- src/zones/azext_zones/custom.py | 2 +- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/zones/azext_zones/_argHelper.py b/src/zones/azext_zones/_argHelper.py index efea5788058..c2b79298927 100644 --- a/src/zones/azext_zones/_argHelper.py +++ b/src/zones/azext_zones/_argHelper.py @@ -25,13 +25,9 @@ def build_arg_query(resource_groups, tags): query = "Resources" if resource_groups is not None and len(resource_groups) > 0: - # Replace spaces with commas in resource groups, in case someone passed a space-separated list - resource_groups = resource_groups.replace(' ', ',') query += " | where resourceGroup in ({0})".format(','.join(f"'{item}'" for item in resource_groups.split(','))) if tags is not None: - # Replace spaces with commas in tags, in case someone passed a space-separated list - tags = tags.replace(' ', ',') tagquery = [] for tag in tags.split(','): tag = tag.strip() diff --git a/src/zones/azext_zones/_validators.py b/src/zones/azext_zones/_validators.py index 34913fb394d..f43bd3d1642 100644 --- a/src/zones/azext_zones/_validators.py +++ b/src/zones/azext_zones/_validators.py @@ -2,3 +2,35 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- + +import re + + +def validate_command_args(namespace): + # Validate resource group input: + if not is_valid_resource_group_list(namespace.resource_group_names): + raise ValueError( + "Resource Groups must be single resource group name or a comma-separated list of valid resource groups" + ) + + # Validate tags input: + if not is_valid_tags_list(namespace.tags): + raise ValueError( + "Tags must be a comma-separated list of key-value pairs in the format 'key=value'" + ) + + +def is_valid_resource_group_list(input_string): + if input_string is None: + return True + pattern = r"^(?!.*\.\.)(?!.*\.$)[\w\-\.\(\)]{1,90}$" + names = [name.strip() for name in input_string.split(",")] + return all(re.match(pattern, name, re.IGNORECASE) for name in names) + + +def is_valid_tags_list(input_string): + if input_string is None: + return True + tag_pattern = r"^[^=,\s][^=,]{0,510}=[^=,]{0,256}$" + tags = [tag.strip() for tag in input_string.split(",")] + return all(re.match(tag_pattern, tag) for tag in tags) diff --git a/src/zones/azext_zones/commands.py b/src/zones/azext_zones/commands.py index 489240074a1..4dc590b584d 100644 --- a/src/zones/azext_zones/commands.py +++ b/src/zones/azext_zones/commands.py @@ -5,9 +5,9 @@ # pylint: disable=line-too-long from azext_zones._client_factory import cf_zones +from azext_zones._validators import validate_command_args def load_command_table(self, _): - with self.command_group('zones', client_factory=cf_zones, is_preview=True) as g: - g.custom_command('validate', 'validate_zones') + g.custom_command('validate', 'validate_zones', validator=validate_command_args) diff --git a/src/zones/azext_zones/custom.py b/src/zones/azext_zones/custom.py index 8ab4141b39e..5799577f86f 100644 --- a/src/zones/azext_zones/custom.py +++ b/src/zones/azext_zones/custom.py @@ -28,7 +28,7 @@ def validate_zones(client, cmd, omit_dependent, resource_group_names, tags): def validate_resources(cmd, resources, omit_dependent=False): resource_results = [] if resources['count'] == 0: - errMsg = ("No resources found, validation could not be run.") + errMsg = ("No resources found with the supplied resource group and tag filters, validation could not be run.") __logger.error(errMsg) # Get the location data we'll use to validate the resources