1# -------------------------------------------------------------------------------------------- 2# Copyright (c) Microsoft Corporation. All rights reserved. 3# Licensed under the MIT License. See License.txt in the project root for license information. 4# -------------------------------------------------------------------------------------------- 5 6from azure.cli.core.commands.validators import validate_tags, get_default_location_from_resource_group 7from azure.cli.core.azclierror import RequiredArgumentMissingError, InvalidArgumentValueError 8 9from knack.util import CLIError 10 11 12def process_autoscale_create_namespace(cmd, namespace): 13 from msrestazure.tools import parse_resource_id 14 15 validate_tags(namespace) 16 get_target_resource_validator('resource', required=True, preserve_resource_group_parameter=True)(cmd, namespace) 17 if not namespace.resource_group_name: 18 namespace.resource_group_name = parse_resource_id(namespace.resource).get('resource_group', None) 19 get_default_location_from_resource_group(cmd, namespace) 20 21 22def validate_autoscale_recurrence(namespace): 23 from azure.mgmt.monitor.models import Recurrence, RecurrentSchedule, RecurrenceFrequency 24 25 def _validate_weekly_recurrence(namespace): 26 # Construct days 27 valid_days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] 28 days = [] 29 for partial in namespace.recurrence[1:]: 30 if len(partial) < 2: 31 raise CLIError('specifying fewer than 2 characters for day is ambiguous.') 32 try: 33 match = next(x for x in valid_days if x.lower().startswith(partial.lower())) 34 except StopIteration: 35 raise CLIError("No match for day '{}'.".format(partial)) 36 days.append(match) 37 valid_days.remove(match) 38 39 # validate, but don't process start and end time 40 recurrence_obj = Recurrence( 41 frequency=RecurrenceFrequency.week, 42 schedule=RecurrentSchedule( 43 time_zone=namespace.timezone, 44 days=days, 45 hours=[], # will be filled in during custom command 46 minutes=[] # will be filled in during custom command 47 ) 48 ) 49 return recurrence_obj 50 51 valid_recurrence = { 52 'week': { 53 'usage': '-r week [DAY DAY ...]', 54 'validator': _validate_weekly_recurrence 55 } 56 } 57 if namespace.recurrence: 58 raw_values = namespace.recurrence 59 try: 60 delimiter = raw_values[0].lower() 61 usage = valid_recurrence[delimiter]['usage'] 62 try: 63 namespace.recurrence = valid_recurrence[delimiter]['validator'](namespace) 64 except CLIError as ex: 65 raise CLIError('{} invalid usage: {}'.format(ex, usage)) 66 except KeyError: 67 raise CLIError('invalid usage: -r {{{}}} [ARG ARG ...]'.format(','.join(valid_recurrence))) 68 69 70def validate_autoscale_timegrain(namespace): 71 from azure.mgmt.monitor.models import MetricTrigger 72 from azure.cli.command_modules.monitor.actions import get_period_type 73 from azure.cli.command_modules.monitor.util import get_autoscale_statistic_map 74 75 values = namespace.timegrain 76 if len(values) == 1: 77 # workaround because CMD.exe eats > character... Allows condition to be 78 # specified as a quoted expression 79 values = values[0].split(' ') 80 name_offset = 0 81 try: 82 time_grain = get_period_type()(values[1]) 83 name_offset += 1 84 except ValueError: 85 time_grain = get_period_type()('1m') 86 try: 87 statistic = get_autoscale_statistic_map()[values[0]] 88 name_offset += 1 89 except KeyError: 90 statistic = get_autoscale_statistic_map()['avg'] 91 timegrain = MetricTrigger( 92 metric_name=None, 93 metric_resource_uri=None, 94 time_grain=time_grain, 95 statistic=statistic, 96 time_window=None, 97 time_aggregation=None, 98 operator=None, 99 threshold=None 100 ) 101 namespace.timegrain = timegrain 102 103 104def get_target_resource_validator(dest, required, preserve_resource_group_parameter=False, alias='resource'): 105 def _validator(cmd, namespace): 106 from msrestazure.tools import is_valid_resource_id 107 name_or_id = getattr(namespace, dest) 108 rg = namespace.resource_group_name 109 res_ns = namespace.namespace 110 parent = namespace.parent 111 res_type = namespace.resource_type 112 113 usage_error = CLIError('usage error: --{0} ID | --{0} NAME --resource-group NAME ' 114 '--{0}-type TYPE [--{0}-parent PARENT] ' 115 '[--{0}-namespace NAMESPACE]'.format(alias)) 116 if not name_or_id and required: 117 raise usage_error 118 if name_or_id: 119 if is_valid_resource_id(name_or_id) and any((res_ns, parent, res_type)): 120 raise usage_error 121 if not is_valid_resource_id(name_or_id): 122 from azure.cli.core.commands.client_factory import get_subscription_id 123 if res_type and '/' in res_type: 124 res_ns = res_ns or res_type.rsplit('/', 1)[0] 125 res_type = res_type.rsplit('/', 1)[1] 126 if not all((rg, res_ns, res_type, name_or_id)): 127 raise usage_error 128 129 setattr(namespace, dest, 130 '/subscriptions/{}/resourceGroups/{}/providers/{}/{}{}/{}'.format( 131 get_subscription_id(cmd.cli_ctx), rg, res_ns, parent + '/' if parent else '', 132 res_type, name_or_id)) 133 134 del namespace.namespace 135 del namespace.parent 136 del namespace.resource_type 137 if not preserve_resource_group_parameter: 138 del namespace.resource_group_name 139 140 return _validator 141 142 143def validate_metrics_alert_dimension(namespace): 144 from azure.cli.command_modules.monitor.grammar.metric_alert.MetricAlertConditionValidator import dim_op_conversion 145 for keyword, value in dim_op_conversion.items(): 146 if namespace.operator == value: 147 namespace.operator = keyword 148 149 150def validate_metrics_alert_condition(namespace): 151 from azure.cli.command_modules.monitor.grammar.metric_alert.MetricAlertConditionValidator import op_conversion, \ 152 agg_conversion, sens_conversion 153 for keyword, value in agg_conversion.items(): 154 if namespace.aggregation == value: 155 namespace.aggregation = keyword 156 break 157 for keyword, value in op_conversion.items(): 158 if namespace.operator == value: 159 namespace.operator = keyword 160 break 161 162 if namespace.condition_type == 'static': 163 if namespace.threshold is None: 164 raise RequiredArgumentMissingError('Parameter --threshold is required for static threshold.') 165 if namespace.operator not in ('=', '!=', '>', '>=', '<', '<='): 166 raise InvalidArgumentValueError('Parameter --operator {} is invalid for static threshold.'.format( 167 op_conversion[namespace.operator] 168 )) 169 elif namespace.condition_type == 'dynamic': 170 if namespace.operator not in ('>', '<', '><'): 171 raise InvalidArgumentValueError('Parameter --operator {} is invalid for dynamic threshold.'.format( 172 op_conversion[namespace.operator] 173 )) 174 if namespace.alert_sensitivity is None: 175 raise RequiredArgumentMissingError('Parameter --sensitivity is required for dynamic threshold.') 176 for keyword, value in sens_conversion.items(): 177 if namespace.alert_sensitivity == value: 178 namespace.alert_sensitivity = keyword 179 break 180 181 if namespace.number_of_evaluation_periods is None: 182 setattr(namespace, 'number_of_evaluation_periods', 4) 183 184 if namespace.number_of_evaluation_periods < 1 or namespace.number_of_evaluation_periods > 6: 185 raise InvalidArgumentValueError('Parameter --num-periods {} should in range 1-6.'.format( 186 namespace.number_of_evaluation_periods 187 )) 188 189 if namespace.min_failing_periods_to_alert is None: 190 setattr(namespace, 'min_failing_periods_to_alert', min(4, namespace.number_of_evaluation_periods)) 191 192 if namespace.min_failing_periods_to_alert < 1 or namespace.min_failing_periods_to_alert > 6: 193 raise InvalidArgumentValueError('Parameter --num-violations {} should in range 1-6.'.format( 194 namespace.min_failing_periods_to_alert 195 )) 196 197 if namespace.min_failing_periods_to_alert > namespace.number_of_evaluation_periods: 198 raise InvalidArgumentValueError( 199 'Parameter --num-violations {} should be less than or equal to parameter --num-periods {}.'.format( 200 namespace.min_failing_periods_to_alert, namespace.number_of_evaluation_periods)) 201 else: 202 raise NotImplementedError() 203 204 205def validate_diagnostic_settings(cmd, namespace): 206 from azure.cli.core.commands.client_factory import get_subscription_id 207 from msrestazure.tools import is_valid_resource_id, resource_id, parse_resource_id 208 209 get_target_resource_validator('resource_uri', required=True, preserve_resource_group_parameter=True)(cmd, namespace) 210 if not namespace.resource_group_name: 211 namespace.resource_group_name = parse_resource_id(namespace.resource_uri)['resource_group'] 212 213 if namespace.storage_account and not is_valid_resource_id(namespace.storage_account): 214 namespace.storage_account = resource_id(subscription=get_subscription_id(cmd.cli_ctx), 215 resource_group=namespace.resource_group_name, 216 namespace='microsoft.Storage', 217 type='storageAccounts', 218 name=namespace.storage_account) 219 220 if namespace.workspace and not is_valid_resource_id(namespace.workspace): 221 namespace.workspace = resource_id(subscription=get_subscription_id(cmd.cli_ctx), 222 resource_group=namespace.resource_group_name, 223 namespace='microsoft.OperationalInsights', 224 type='workspaces', 225 name=namespace.workspace) 226 227 if namespace.event_hub and is_valid_resource_id(namespace.event_hub): 228 namespace.event_hub = parse_resource_id(namespace.event_hub)['name'] 229 230 if namespace.event_hub_rule: 231 if not is_valid_resource_id(namespace.event_hub_rule): 232 if not namespace.event_hub: 233 raise CLIError('usage error: --event-hub-rule ID | --event-hub-rule NAME --event-hub NAME') 234 # use value from --event-hub if the rule is a name 235 namespace.event_hub_rule = resource_id( 236 subscription=get_subscription_id(cmd.cli_ctx), 237 resource_group=namespace.resource_group_name, 238 namespace='Microsoft.EventHub', 239 type='namespaces', 240 name=namespace.event_hub, 241 child_type_1='AuthorizationRules', 242 child_name_1=namespace.event_hub_rule) 243 elif not namespace.event_hub: 244 # extract the event hub name from `--event-hub-rule` if provided as an ID 245 namespace.event_hub = parse_resource_id(namespace.event_hub_rule)['name'] 246 247 if not any([namespace.storage_account, namespace.workspace, namespace.event_hub]): 248 raise CLIError( 249 'usage error - expected one or more: --storage-account NAME_OR_ID | --workspace NAME_OR_ID ' 250 '| --event-hub NAME_OR_ID | --event-hub-rule ID') 251 252 try: 253 del namespace.resource_group_name 254 except AttributeError: 255 pass 256 257 258def _validate_tags(namespace): 259 """ Extracts multiple space-separated tags in key[=value] format """ 260 if isinstance(namespace.tags, list): 261 tags_dict = {} 262 for item in namespace.tags: 263 tags_dict.update(_validate_tag(item)) 264 namespace.tags = tags_dict 265 266 267def _validate_tag(string): 268 """ Extracts a single tag in key[=value] format """ 269 result = {} 270 if string: 271 comps = string.split('=', 1) 272 result = {comps[0]: comps[1]} if len(comps) > 1 else {string: ''} 273 return result 274 275 276def process_action_group_detail_for_creation(namespace): 277 from azure.mgmt.monitor.models import ActionGroupResource, EmailReceiver, SmsReceiver, WebhookReceiver, \ 278 ArmRoleReceiver, AzureAppPushReceiver, ItsmReceiver, AutomationRunbookReceiver, \ 279 VoiceReceiver, LogicAppReceiver, AzureFunctionReceiver 280 281 _validate_tags(namespace) 282 283 ns = vars(namespace) 284 name = ns['action_group_name'] 285 receivers = ns.pop('receivers') or [] 286 action_group_resource_properties = { 287 'location': 'global', # as of now, 'global' is the only available location for action group 288 'group_short_name': ns.pop('short_name') or name[:12], # '12' is the short name length limitation 289 'email_receivers': [r for r in receivers if isinstance(r, EmailReceiver)], 290 'sms_receivers': [r for r in receivers if isinstance(r, SmsReceiver)], 291 'webhook_receivers': [r for r in receivers if isinstance(r, WebhookReceiver)], 292 'arm_role_receivers': [r for r in receivers if isinstance(r, ArmRoleReceiver)], 293 'itsm_receivers': [r for r in receivers if isinstance(r, ItsmReceiver)], 294 'azure_app_push_receivers': [r for r in receivers if isinstance(r, AzureAppPushReceiver)], 295 'automation_runbook_receivers': [r for r in receivers if isinstance(r, AutomationRunbookReceiver)], 296 'voice_receivers': [r for r in receivers if isinstance(r, VoiceReceiver)], 297 'logic_app_receivers': [r for r in receivers if isinstance(r, LogicAppReceiver)], 298 'azure_function_receivers': [r for r in receivers if isinstance(r, AzureFunctionReceiver)], 299 'tags': ns.get('tags') or None 300 } 301 if hasattr(namespace, 'tags'): 302 del namespace.tags 303 304 ns['action_group'] = ActionGroupResource(**action_group_resource_properties) 305 306 307def validate_metric_dimension(namespace): 308 309 if not namespace.dimension: 310 return 311 312 if namespace.filters: 313 raise CLIError('usage: --dimension and --filter parameters are mutually exclusive.') 314 315 namespace.filters = ' and '.join("{} eq '*'".format(d) for d in namespace.dimension) 316 317 318def process_webhook_prop(namespace): 319 if not isinstance(namespace.webhook_properties, list): 320 return 321 322 result = {} 323 for each in namespace.webhook_properties: 324 if each: 325 if '=' in each: 326 key, value = each.split('=', 1) 327 else: 328 key, value = each, '' 329 result[key] = value 330 331 namespace.webhook_properties = result 332 333 334def get_action_group_validator(dest): 335 def validate_action_groups(cmd, namespace): 336 action_groups = getattr(namespace, dest, None) 337 338 if not action_groups: 339 return 340 341 from msrestazure.tools import is_valid_resource_id, resource_id 342 from azure.cli.core.commands.client_factory import get_subscription_id 343 344 subscription = get_subscription_id(cmd.cli_ctx) 345 resource_group = namespace.resource_group_name 346 for group in action_groups: 347 if not is_valid_resource_id(group.action_group_id): 348 group.action_group_id = resource_id( 349 subscription=subscription, 350 resource_group=resource_group, 351 namespace='microsoft.insights', 352 type='actionGroups', 353 name=group.action_group_id 354 ) 355 return validate_action_groups 356 357 358def get_action_group_id_validator(dest): 359 def validate_action_group_ids(cmd, namespace): 360 action_groups = getattr(namespace, dest, None) 361 362 if not action_groups: 363 return 364 365 from msrestazure.tools import is_valid_resource_id, resource_id 366 from azure.cli.core.commands.client_factory import get_subscription_id 367 368 action_group_ids = [] 369 subscription = get_subscription_id(cmd.cli_ctx) 370 resource_group = namespace.resource_group_name 371 for group in action_groups: 372 if not is_valid_resource_id(group): 373 group = resource_id( 374 subscription=subscription, 375 resource_group=resource_group, 376 namespace='microsoft.insights', 377 type='actionGroups', 378 name=group 379 ) 380 action_group_ids.append(group.lower()) 381 setattr(namespace, dest, action_group_ids) 382 return validate_action_group_ids 383 384 385def validate_private_endpoint_connection_id(namespace): 386 if namespace.connection_id: 387 from azure.cli.core.util import parse_proxy_resource_id 388 result = parse_proxy_resource_id(namespace.connection_id) 389 namespace.resource_group_name = result['resource_group'] 390 namespace.scope_name = result['name'] 391 namespace.private_endpoint_connection_name = result['child_name_1'] 392 393 if not all([namespace.scope_name, namespace.resource_group_name, namespace.private_endpoint_connection_name]): 394 raise CLIError('incorrect usage. Please provide [--id ID] or [--name NAME --scope-name NAME -g NAME]') 395 396 del namespace.connection_id 397 398 399def validate_storage_accounts_name_or_id(cmd, namespace): 400 if namespace.storage_account_ids: 401 from msrestazure.tools import is_valid_resource_id, resource_id 402 from azure.cli.core.commands.client_factory import get_subscription_id 403 for index, storage_account_id in enumerate(namespace.storage_account_ids): 404 if not is_valid_resource_id(storage_account_id): 405 namespace.storage_account_ids[index] = resource_id( 406 subscription=get_subscription_id(cmd.cli_ctx), 407 resource_group=namespace.resource_group_name, 408 namespace='Microsoft.Storage', 409 type='storageAccounts', 410 name=storage_account_id 411 ) 412 413 414def process_subscription_id(cmd, namespace): 415 from azure.cli.core.commands.client_factory import get_subscription_id 416 namespace.subscription_id = get_subscription_id(cmd.cli_ctx) 417 418 419def process_workspace_data_export_destination(namespace): 420 if namespace.destination: 421 from azure.mgmt.core.tools import is_valid_resource_id, resource_id, parse_resource_id 422 if not is_valid_resource_id(namespace.destination): 423 raise CLIError('usage error: --destination should be a storage account, ' 424 'an evenhug namespace or an event hub resource id.') 425 result = parse_resource_id(namespace.destination) 426 if result['namespace'].lower() == 'microsoft.storage' and result['type'].lower() == 'storageaccounts': 427 namespace.data_export_type = 'StorageAccount' 428 elif result['namespace'].lower() == 'microsoft.eventhub' and result['type'].lower() == 'namespaces': 429 namespace.data_export_type = 'EventHub' 430 namespace.destination = resource_id( 431 subscription=result['subscription'], 432 resource_group=result['resource_group'], 433 namespace=result['namespace'], 434 type=result['type'], 435 name=result['name'] 436 ) 437 if 'child_type_1' in result and result['child_type_1'].lower() == 'eventhubs': 438 namespace.event_hub_name = result['child_name_1'] 439 else: 440 raise CLIError('usage error: --destination should be a storage account, ' 441 'an evenhug namespace or an event hub resource id.') 442