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 typing import (Optional, List)
7from azure.mgmt.cdn.models import (AFDEndpoint, HealthProbeRequestType, EnabledState, Route, LinkToDefaultDomain,
8                                   ResourceReference, AFDEndpointProtocols, HttpsRedirect, ForwardingProtocol,
9                                   QueryStringCachingBehavior, HealthProbeParameters, MatchProcessingBehavior,
10                                   AFDOrigin, AFDOriginGroup, SharedPrivateLinkResourceProperties, CompressionSettings,
11                                   LoadBalancingSettingsParameters, SecurityPolicyWebApplicationFirewallParameters,
12                                   SecurityPolicyWebApplicationFirewallAssociation, CustomerCertificateParameters,
13                                   AFDDomain, AFDDomainHttpsParameters, AfdCertificateType, AfdMinimumTlsVersion,
14                                   AFDEndpointUpdateParameters, SkuName, AfdPurgeParameters, Secret,
15                                   SecurityPolicy, ProfileUpdateParameters)
16
17from azure.mgmt.cdn.operations import (AFDOriginGroupsOperations, AFDOriginsOperations, AFDProfilesOperations,
18                                       SecretsOperations, AFDEndpointsOperations, RoutesOperations, RuleSetsOperations,
19                                       RulesOperations, SecurityPoliciesOperations, AFDCustomDomainsOperations,
20                                       ProfilesOperations)
21
22from azure.cli.core.commands.client_factory import get_subscription_id
23from azure.cli.core.util import (sdk_no_wait)
24from azure.cli.core.azclierror import (InvalidArgumentValueError)
25from azure.core.exceptions import (ResourceNotFoundError)
26
27from knack.log import get_logger
28from msrest.polling import LROPoller, NoPolling
29
30from .custom import _update_mapper
31
32logger = get_logger(__name__)
33
34
35def default_content_types():
36    return ["application/eot",
37            "application/font",
38            "application/font-sfnt",
39            "application/javascript",
40            "application/json",
41            "application/opentype",
42            "application/otf",
43            "application/pkcs7-mime",
44            "application/truetype",
45            "application/ttf",
46            "application/vnd.ms-fontobject",
47            "application/xhtml+xml",
48            "application/xml",
49            "application/xml+rss",
50            "application/x-font-opentype",
51            "application/x-font-truetype",
52            "application/x-font-ttf",
53            "application/x-httpd-cgi",
54            "application/x-javascript",
55            "application/x-mpegurl",
56            "application/x-opentype",
57            "application/x-otf",
58            "application/x-perl",
59            "application/x-ttf",
60            "font/eot",
61            "font/ttf",
62            "font/otf",
63            "font/opentype",
64            "image/svg+xml",
65            "text/css",
66            "text/csv",
67            "text/html",
68            "text/javascript",
69            "text/js",
70            "text/plain",
71            "text/richtext",
72            "text/tab-separated-values",
73            "text/xml",
74            "text/x-script",
75            "text/x-component",
76            "text/x-java-source"]
77
78
79def create_afd_profile(client: ProfilesOperations, resource_group_name, profile_name,
80                       sku: SkuName,
81                       tags=None):
82    from azure.mgmt.cdn.models import (Profile, Sku)
83
84    # Force location to global
85    profile = Profile(location="global", sku=Sku(name=sku), tags=tags)
86    return client.begin_create(resource_group_name, profile_name, profile)
87
88
89def delete_afd_profile(client: ProfilesOperations, resource_group_name, profile_name):
90    profile = None
91    try:
92        profile = client.get(resource_group_name, profile_name)
93    except ResourceNotFoundError:
94        pass
95
96    if profile is None or profile.sku.name not in (SkuName.premium_azure_front_door,
97                                                   SkuName.standard_azure_front_door):
98        def get_long_running_output(_):
99            return None
100
101        logger.warning("Unexpected SKU type, only Standard_AzureFrontDoor and Premium_AzureFrontDoor are supported.")
102        return LROPoller(client, None, get_long_running_output, NoPolling())
103
104    return client.begin_delete(resource_group_name, profile_name)
105
106
107def update_afd_profile(client: ProfilesOperations, resource_group_name, profile_name, tags):
108    profile = client.get(resource_group_name, profile_name)
109    if profile.sku.name not in (SkuName.premium_azure_front_door, SkuName.standard_azure_front_door):
110        logger.warning('Unexpected SKU type, only Standard_AzureFrontDoor and Premium_AzureFrontDoor are supported')
111        raise ResourceNotFoundError("Operation returned an invalid status code 'Not Found'")
112
113    return client.begin_update(resource_group_name, profile_name, ProfileUpdateParameters(tags=tags))
114
115
116def list_afd_profiles(client: ProfilesOperations, resource_group_name=None):
117    profile_list = client.list_by_resource_group(resource_group_name=resource_group_name) \
118        if resource_group_name else client.list()
119
120    profile_list = [profile for profile in profile_list if profile.sku.name in
121                    (SkuName.premium_azure_front_door, SkuName.standard_azure_front_door)]
122
123    return list(profile_list)
124
125
126def get_afd_profile(client: ProfilesOperations, resource_group_name, profile_name):
127    profile = client.get(resource_group_name, profile_name)
128    if profile.sku.name not in (SkuName.premium_azure_front_door, SkuName.standard_azure_front_door):
129        # Workaround to make the behavior consist with true "Not Found"
130        logger.warning('Unexpected SKU type, only Standard_AzureFrontDoor and Premium_AzureFrontDoor are supported')
131        raise ResourceNotFoundError("Operation returned an invalid status code 'Not Found'")
132
133    return profile
134
135
136def list_resource_usage(client: AFDProfilesOperations, resource_group_name, profile_name):
137    return client.list_resource_usage(resource_group_name, profile_name)
138
139
140def create_afd_endpoint(client: AFDEndpointsOperations, resource_group_name, profile_name, endpoint_name,
141                        origin_response_timeout_seconds,
142                        enabled_state, tags=None, no_wait=None):
143
144    # Force location to global
145    endpoint = AFDEndpoint(location="global",
146                           origin_response_timeout_seconds=origin_response_timeout_seconds,
147                           enabled_state=enabled_state,
148                           tags=tags)
149
150    return sdk_no_wait(no_wait, client.begin_create, resource_group_name, profile_name, endpoint_name, endpoint)
151
152
153def purge_afd_endpoint_content(client: AFDEndpointsOperations, resource_group_name, profile_name, endpoint_name,
154                               content_paths, domains=None, no_wait=None):
155    endpoint = AfdPurgeParameters(content_paths=content_paths,
156                                  domains=domains)
157
158    return sdk_no_wait(no_wait, client.begin_purge_content, resource_group_name, profile_name, endpoint_name, endpoint)
159
160
161def update_afd_endpoint(client: AFDEndpointsOperations, resource_group_name, profile_name, endpoint_name,
162                        origin_response_timeout_seconds=None, enabled_state=None, tags=None):
163    update_properties = AFDEndpointUpdateParameters(
164        origin_response_timeout_seconds=origin_response_timeout_seconds,
165        enabled_state=enabled_state,
166        tags=tags
167    )
168
169    return client.begin_update(resource_group_name, profile_name, endpoint_name, update_properties)
170
171
172def create_afd_origin_group(client: AFDOriginGroupsOperations,
173                            resource_group_name: str,
174                            profile_name: str,
175                            origin_group_name: str,
176                            load_balancing_sample_size: int,
177                            load_balancing_successful_samples_required: int,
178                            load_balancing_additional_latency_in_milliseconds: int,
179                            probe_request_type: HealthProbeRequestType,
180                            probe_protocol: str,
181                            probe_path: str,
182                            probe_interval_in_seconds: int = 240):
183
184    # Add response error detection support once RP support it.
185    health_probe_parameters = HealthProbeParameters(probe_path=probe_path,
186                                                    probe_request_type=probe_request_type,
187                                                    probe_protocol=probe_protocol,
188                                                    probe_interval_in_seconds=probe_interval_in_seconds)
189
190    load_balancing_settings_parameters = LoadBalancingSettingsParameters(
191        sample_size=load_balancing_sample_size,
192        successful_samples_required=load_balancing_successful_samples_required,
193        additional_latency_in_milliseconds=load_balancing_additional_latency_in_milliseconds)
194
195    afd_origin_group = AFDOriginGroup(load_balancing_settings=load_balancing_settings_parameters,
196                                      health_probe_settings=health_probe_parameters)
197
198    return client.begin_create(resource_group_name,
199                               profile_name,
200                               origin_group_name,
201                               afd_origin_group).result()
202
203
204def update_afd_origin_group(client: AFDOriginGroupsOperations,
205                            resource_group_name: str,
206                            profile_name: str,
207                            origin_group_name: str,
208                            load_balancing_sample_size: int = None,
209                            load_balancing_successful_samples_required: int = None,
210                            load_balancing_additional_latency_in_milliseconds: int = None,
211                            probe_request_type: HealthProbeRequestType = None,
212                            probe_protocol: str = None,
213                            probe_path: str = None,
214                            probe_interval_in_seconds: int = None):
215
216    # Move these to the parameters list once support is added in RP.
217    existing = client.get(resource_group_name, profile_name, origin_group_name)
218
219    health_probe_parameters = HealthProbeParameters(probe_path=probe_path,
220                                                    probe_request_type=probe_request_type,
221                                                    probe_protocol=probe_protocol,
222                                                    probe_interval_in_seconds=probe_interval_in_seconds)
223
224    _update_mapper(existing.health_probe_settings,
225                   health_probe_parameters,
226                   ["probe_path", "probe_request_type", "probe_protocol", "probe_interval_in_seconds"])
227
228    load_balancing_settings_parameters = LoadBalancingSettingsParameters(
229        sample_size=load_balancing_sample_size,
230        successful_samples_required=load_balancing_successful_samples_required,
231        additional_latency_in_milliseconds=load_balancing_additional_latency_in_milliseconds)
232    _update_mapper(existing.load_balancing_settings,
233                   load_balancing_settings_parameters,
234                   ["sample_size", "successful_samples_required", "additional_latency_in_milliseconds"])
235
236    afd_origin_group = AFDOriginGroup(load_balancing_settings=load_balancing_settings_parameters,
237                                      health_probe_settings=health_probe_parameters)
238
239    return client.begin_create(resource_group_name,
240                               profile_name,
241                               origin_group_name,
242                               afd_origin_group).result()
243
244
245def create_afd_origin(client: AFDOriginsOperations,
246                      resource_group_name: str,
247                      profile_name: str,
248                      origin_group_name: str,
249                      origin_name: str,
250                      host_name: str,
251                      enabled_state: EnabledState,
252                      enable_private_link: bool = None,
253                      private_link_resource: str = None,
254                      private_link_location: str = None,
255                      private_link_sub_resource_type: str = None,
256                      private_link_request_message: str = None,
257                      http_port: int = 80,
258                      https_port: int = 443,
259                      origin_host_header: Optional[str] = None,
260                      priority: int = 1,
261                      weight: int = 1000):
262
263    shared_private_link_resource = None
264    if enable_private_link:
265        shared_private_link_resource = SharedPrivateLinkResourceProperties(
266            private_link=ResourceReference(id=private_link_resource),
267            private_link_location=private_link_location,
268            group_id=private_link_sub_resource_type,
269            request_message=private_link_request_message)
270
271    return client.begin_create(resource_group_name,
272                               profile_name,
273                               origin_group_name,
274                               origin_name,
275                               AFDOrigin(
276                                   host_name=host_name,
277                                   http_port=http_port,
278                                   https_port=https_port,
279                                   origin_host_header=origin_host_header,
280                                   priority=priority,
281                                   weight=weight,
282                                   shared_private_link_resource=shared_private_link_resource,
283                                   enabled_state=enabled_state))
284
285
286def update_afd_origin(client: AFDOriginsOperations,
287                      resource_group_name: str,
288                      profile_name: str,
289                      origin_group_name: str,
290                      origin_name: str,
291                      host_name: str = None,
292                      enabled_state: EnabledState = None,
293                      http_port: int = None,
294                      https_port: int = None,
295                      origin_host_header: Optional[str] = None,
296                      priority: int = None,
297                      weight: int = None,
298                      enable_private_link: bool = None,
299                      private_link_resource: str = None,
300                      private_link_location: str = None,
301                      private_link_sub_resource_type: str = None,
302                      private_link_request_message: str = None):
303
304    existing = client.get(resource_group_name, profile_name, origin_group_name, origin_name)
305    origin = AFDOrigin(
306        host_name=host_name,
307        http_port=http_port,
308        https_port=https_port,
309        origin_host_header=origin_host_header,
310        priority=priority,
311        weight=weight,
312        enabled_state=enabled_state)
313
314    _update_mapper(
315        existing,
316        origin,
317        ["host_name", "http_port", "https_port", "origin_host_header", "priority", "weight", "enabled_state"])
318
319    if enable_private_link is not None and not enable_private_link:
320        origin.shared_private_link_resource = None
321    elif (private_link_resource is not None or
322          private_link_location is not None or
323          private_link_sub_resource_type is not None or
324          private_link_request_message is not None):
325        shared_private_link_resource = SharedPrivateLinkResourceProperties(
326            private_link=ResourceReference(id=private_link_resource) if private_link_resource is not None else None,
327            private_link_location=private_link_location,
328            group_id=private_link_sub_resource_type,
329            request_message=private_link_request_message)
330
331        if existing.shared_private_link_resource is not None:
332            existing_shared_private_link_resource = SharedPrivateLinkResourceProperties(
333                private_link=ResourceReference(id=existing.shared_private_link_resource["privateLink"]['id']),
334                private_link_location=existing.shared_private_link_resource["privateLinkLocation"],
335                group_id=existing.shared_private_link_resource["groupId"],
336                request_message=existing.shared_private_link_resource["requestMessage"])
337
338            _update_mapper(
339                existing_shared_private_link_resource,
340                shared_private_link_resource,
341                ["private_link", "private_link_location", "group_id", "request_message"])
342
343        origin.shared_private_link_resource = shared_private_link_resource
344    else:
345        origin.shared_private_link_resource = existing.shared_private_link_resource
346
347    # client.update does not allow unset field
348    return client.begin_create(resource_group_name,
349                               profile_name,
350                               origin_group_name,
351                               origin_name,
352                               origin)
353
354
355def create_afd_route(cmd,
356                     client: RoutesOperations,
357                     resource_group_name: str,
358                     profile_name: str,
359                     endpoint_name: str,
360                     route_name: str,
361                     https_redirect: HttpsRedirect,
362                     supported_protocols: List[AFDEndpointProtocols],
363                     origin_group: str,
364                     forwarding_protocol: ForwardingProtocol,
365                     link_to_default_domain: LinkToDefaultDomain = None,
366                     is_compression_enabled: bool = False,
367                     content_types_to_compress: List[str] = None,
368                     query_string_caching_behavior: QueryStringCachingBehavior = None,
369                     custom_domains: List[str] = None,
370                     origin_path: Optional[str] = None,
371                     patterns_to_match: List[str] = None,
372                     rule_sets: List[str] = None):
373
374    subscription_id = get_subscription_id(cmd.cli_ctx)
375    formatted_custom_domains = []
376    if custom_domains is not None:
377        for custom_domain in custom_domains:
378            if '/customdomains/' not in custom_domain.lower():
379                custom_domain = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \
380                                f'/providers/Microsoft.Cdn/profiles/{profile_name}/customDomains/{custom_domain}'
381
382            # If the origin is not an ID, assume it's a name and format it as an ID.
383            formatted_custom_domains.append(ResourceReference(id=custom_domain))
384
385    if '/origingroups/' not in origin_group.lower():
386        origin_group = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \
387                       f'/providers/Microsoft.Cdn/profiles/{profile_name}/originGroups/{origin_group}'
388
389    compression_settings = CompressionSettings(
390        content_types_to_compress=content_types_to_compress,
391        is_compression_enabled=is_compression_enabled
392    )
393
394    if is_compression_enabled and content_types_to_compress is None:
395        compression_settings.content_types_to_compress = default_content_types()
396
397    if not compression_settings.is_compression_enabled:
398        compression_settings.content_types_to_compress = []
399
400    formatted_rule_sets = []
401    if rule_sets is not None:
402        for rule_set in rule_sets:
403            if '/rulesets/' not in rule_set.lower():
404                rule_set = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \
405                           f'/providers/Microsoft.Cdn/profiles/{profile_name}/ruleSets/{rule_set}'
406
407            formatted_rule_sets.append(ResourceReference(id=rule_set))
408
409    return client.begin_create(resource_group_name,
410                               profile_name,
411                               endpoint_name,
412                               route_name,
413                               Route(
414                                   custom_domains=formatted_custom_domains,
415                                   origin_path=origin_path,
416                                   patterns_to_match=patterns_to_match if patterns_to_match is not None else ['/*'],
417                                   supported_protocols=supported_protocols,
418                                   https_redirect=https_redirect,
419                                   origin_group=ResourceReference(id=origin_group),
420                                   forwarding_protocol=forwarding_protocol,
421                                   rule_sets=formatted_rule_sets,
422                                   query_string_caching_behavior=query_string_caching_behavior,
423                                   compression_settings=compression_settings,
424                                   link_to_default_domain=LinkToDefaultDomain.enabled if link_to_default_domain else
425                                   LinkToDefaultDomain.disabled))
426
427
428def update_afd_route(cmd,
429                     client: RoutesOperations,
430                     resource_group_name: str,
431                     profile_name: str,
432                     endpoint_name: str,
433                     route_name: str,
434                     https_redirect: HttpsRedirect = None,
435                     supported_protocols: List[AFDEndpointProtocols] = None,
436                     origin_group: str = None,
437                     forwarding_protocol: ForwardingProtocol = None,
438                     link_to_default_domain: LinkToDefaultDomain = None,
439                     is_compression_enabled: bool = None,
440                     content_types_to_compress: List[str] = None,
441                     query_string_caching_behavior: QueryStringCachingBehavior = None,
442                     custom_domains: List[str] = None,
443                     origin_path: Optional[str] = None,
444                     patterns_to_match: List[str] = None,
445                     rule_sets: List[str] = None):
446
447    existing = client.get(resource_group_name, profile_name, endpoint_name, route_name)
448    route = Route(
449        origin_path=origin_path,
450        origin_group=existing.origin_group,
451        patterns_to_match=patterns_to_match,
452        supported_protocols=supported_protocols,
453        https_redirect=https_redirect,
454        forwarding_protocol=forwarding_protocol,
455        query_string_caching_behavior=query_string_caching_behavior,
456        link_to_default_domain=link_to_default_domain)
457
458    subscription_id = get_subscription_id(cmd.cli_ctx)
459    if custom_domains is not None:
460        formatted_custom_domains = []
461        for custom_domain in custom_domains:
462            if '/customdomains/' not in custom_domain.lower():
463                custom_domain = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \
464                                f'/providers/Microsoft.Cdn/profiles/{profile_name}/customDomains/{custom_domain}'
465
466            # If the origin is not an ID, assume it's a name and format it as an ID.
467            formatted_custom_domains.append(ResourceReference(id=custom_domain))
468
469        route.custom_domains = formatted_custom_domains
470
471    if origin_group is not None:
472        if '/origingroups/' not in origin_group.lower():
473            origin_group = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \
474                           f'/providers/Microsoft.Cdn/profiles/{profile_name}/originGroups/{origin_group}'
475
476        route.origin_group = origin_group
477
478    if rule_sets is not None:
479        formatted_rule_sets = []
480        for rule_set in rule_sets:
481            if '/rulesets/' not in rule_set.lower():
482                rule_set = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \
483                           f'/providers/Microsoft.Cdn/profiles/{profile_name}/ruleSets/{rule_set}'
484
485            # If the origin is not an ID, assume it's a name and format it as an ID.
486            formatted_rule_sets.append(ResourceReference(id=rule_set))
487
488        route.rule_sets = formatted_rule_sets
489
490    _update_mapper(existing, route,
491                   ["custom_domains", "origin_path", "patterns_to_match",
492                    "supported_protocols", "https_redirect", "origin_group",
493                    "forwarding_protocol", "rule_sets", "query_string_caching_behavior",
494                    "link_to_default_domain", "compression_settings"])
495
496    if is_compression_enabled:
497        if existing.compression_settings is None:
498            route.compression_settings = CompressionSettings(
499                content_types_to_compress=content_types_to_compress if content_types_to_compress is not None else
500                default_content_types(),
501                is_compression_enabled=is_compression_enabled
502            )
503        else:
504            route.compression_settings = CompressionSettings(
505                content_types_to_compress=content_types_to_compress if content_types_to_compress is not None else
506                existing.compression_settings.content_types_to_compress,
507                is_compression_enabled=is_compression_enabled
508            )
509    elif is_compression_enabled is None:
510        if content_types_to_compress is not None and existing.compression_settings is not None:
511            route.compression_settings = CompressionSettings(
512                content_types_to_compress=content_types_to_compress,
513                is_compression_enabled=existing.compression_settings["isCompressionEnabled"]
514            )
515    else:
516        route.compression_settings = CompressionSettings(
517            content_types_to_compress=[],
518            is_compression_enabled=False
519        )
520
521    return client.begin_create(resource_group_name,
522                               profile_name,
523                               endpoint_name,
524                               route_name,
525                               route)
526
527
528def create_afd_rule_set(client: RuleSetsOperations,
529                        resource_group_name: str,
530                        profile_name: str,
531                        rule_set_name: str):
532
533    return client.begin_create(resource_group_name, profile_name, rule_set_name)
534
535
536# pylint: disable=too-many-locals
537def create_afd_rule(client: RulesOperations, resource_group_name, profile_name, rule_set_name,
538                    order, rule_name, action_name, match_variable=None, operator=None,
539                    match_values=None, selector=None, negate_condition=None, transform=None,
540                    cache_behavior=None, cache_duration=None, header_action=None,
541                    header_name=None, header_value=None, query_string_behavior=None, query_parameters=None,
542                    redirect_type=None, redirect_protocol=None, custom_hostname=None, custom_path=None,
543                    custom_querystring=None, custom_fragment=None, source_pattern=None,
544                    destination=None, preserve_unmatched_path=None,
545                    match_processing_behavior: MatchProcessingBehavior = None):
546    from azure.mgmt.cdn.models import Rule
547    from .custom import create_condition
548    from .custom import create_action
549
550    conditions = []
551    condition = create_condition(match_variable, operator, match_values, selector, negate_condition, transform)
552    if condition is not None:
553        conditions.append(condition)
554
555    actions = []
556    action = create_action(action_name, cache_behavior, cache_duration, header_action, header_name,
557                           header_value, query_string_behavior, query_parameters, redirect_type,
558                           redirect_protocol, custom_hostname, custom_path, custom_querystring,
559                           custom_fragment, source_pattern, destination, preserve_unmatched_path)
560    if action is not None:
561        actions.append(action)
562
563    rule = Rule(
564        name=rule_name,
565        order=order,
566        conditions=conditions,
567        actions=actions,
568        match_processing_behavior=match_processing_behavior
569    )
570
571    return client.begin_create(resource_group_name,
572                               profile_name,
573                               rule_set_name,
574                               rule_name,
575                               rule=rule)
576
577
578def add_afd_rule_condition(client: RulesOperations, resource_group_name, profile_name, rule_set_name,
579                           rule_name, match_variable, operator, match_values=None, selector=None,
580                           negate_condition=None, transform=None):
581    from .custom import create_condition
582
583    existing_rule = client.get(resource_group_name, profile_name, rule_set_name, rule_name)
584    condition = create_condition(match_variable, operator, match_values, selector, negate_condition, transform)
585    existing_rule.conditions.append(condition)
586
587    return client.begin_create(resource_group_name,
588                               profile_name,
589                               rule_set_name,
590                               rule_name,
591                               rule=existing_rule)
592
593
594def add_afd_rule_action(client: RulesOperations, resource_group_name, profile_name, rule_set_name,
595                        rule_name, action_name, cache_behavior=None, cache_duration=None,
596                        header_action=None, header_name=None, header_value=None, query_string_behavior=None,
597                        query_parameters=None, redirect_type=None, redirect_protocol=None, custom_hostname=None,
598                        custom_path=None, custom_querystring=None, custom_fragment=None, source_pattern=None,
599                        destination=None, preserve_unmatched_path=None):
600    from .custom import create_action
601
602    existing_rule = client.get(resource_group_name, profile_name, rule_set_name, rule_name)
603    action = create_action(action_name, cache_behavior, cache_duration, header_action, header_name,
604                           header_value, query_string_behavior, query_parameters, redirect_type,
605                           redirect_protocol, custom_hostname, custom_path, custom_querystring,
606                           custom_fragment, source_pattern, destination, preserve_unmatched_path)
607
608    existing_rule.actions.append(action)
609    return client.begin_create(resource_group_name,
610                               profile_name,
611                               rule_set_name,
612                               rule_name,
613                               rule=existing_rule)
614
615
616def remove_afd_rule_condition(client: RulesOperations, resource_group_name, profile_name,
617                              rule_set_name, rule_name, index):
618    existing_rule = client.get(resource_group_name, profile_name, rule_set_name, rule_name)
619    if len(existing_rule.conditions) > 1 and index < len(existing_rule.conditions):
620        existing_rule.conditions.pop(index)
621    else:
622        logger.warning("Invalid condition index found. This command will be skipped. Please check the rule.")
623
624    return client.begin_create(resource_group_name,
625                               profile_name,
626                               rule_set_name,
627                               rule_name,
628                               rule=existing_rule)
629
630
631def remove_afd_rule_action(client: RulesOperations, resource_group_name, profile_name, rule_set_name, rule_name, index):
632    existing_rule = client.get(resource_group_name, profile_name, rule_set_name, rule_name)
633    if len(existing_rule.actions) > 1 and index < len(existing_rule.actions):
634        existing_rule.actions.pop(index)
635    else:
636        logger.warning("Invalid condition index found. This command will be skipped. Please check the rule.")
637
638    return client.begin_create(resource_group_name,
639                               profile_name,
640                               rule_set_name,
641                               rule_name,
642                               rule=existing_rule)
643
644
645def list_afd_rule_condition(client: RulesOperations, resource_group_name,
646                            profile_name, rule_set_name,
647                            rule_name):
648    rule = client.get(resource_group_name, profile_name, rule_set_name, rule_name)
649    return rule.conditions
650
651
652def list_afd_rule_action(client: RulesOperations, resource_group_name,
653                         profile_name, rule_set_name,
654                         rule_name):
655    rule = client.get(resource_group_name, profile_name, rule_set_name, rule_name)
656    return rule.actions
657
658
659def create_afd_security_policy(client: SecurityPoliciesOperations,
660                               resource_group_name,
661                               profile_name,
662                               security_policy_name,
663                               domains: List[str],
664                               waf_policy: str):
665
666    if any(("/afdendpoints/" not in domain.lower() and
667            "/customdomains/" not in domain.lower()) for domain in domains):
668        raise InvalidArgumentValueError('Domain should either be endpoint ID or custom domain ID.')
669
670    if "/frontdoorwebapplicationfirewallpolicies/" not in waf_policy.lower():
671        raise InvalidArgumentValueError('waf_policy should be valid Front Door WAF policy ID.')
672
673    # Add patterns and multiple domains support in the feature
674    parameters = SecurityPolicyWebApplicationFirewallParameters(
675        waf_policy=ResourceReference(id=waf_policy),
676        associations=[SecurityPolicyWebApplicationFirewallAssociation(
677            domains=[ResourceReference(id=domain) for domain in domains],
678            patterns_to_match=["/*"])])
679
680    return client.begin_create(resource_group_name,
681                               profile_name,
682                               security_policy_name,
683                               SecurityPolicy(parameters=parameters))
684
685
686def update_afd_security_policy(client: SecurityPoliciesOperations,
687                               resource_group_name,
688                               profile_name,
689                               security_policy_name,
690                               domains: List[str] = None,
691                               waf_policy: str = None):
692
693    if domains is not None and any(("/afdendpoints/" not in domain.lower() and
694                                    "/customdomains/" not in domain.lower()) for domain in domains):
695        raise InvalidArgumentValueError('Domain should be either endpoint ID or custom domain ID.')
696
697    if waf_policy is not None and "/frontdoorwebapplicationfirewallpolicies/" not in waf_policy:
698        raise InvalidArgumentValueError('waf_policy should be Front Door WAF policy ID.')
699
700    existing = client.get(resource_group_name, profile_name, security_policy_name)
701
702    # Add patterns and multiple domains support in the future
703    parameters = SecurityPolicyWebApplicationFirewallParameters(
704        waf_policy=ResourceReference(id=waf_policy) if waf_policy is not None else existing.parameters.waf_policy,
705        associations=[SecurityPolicyWebApplicationFirewallAssociation(
706            domains=[ResourceReference(id=domain) for domain in domains],
707            patterns_to_match=["/*"])] if domains is not None else existing.parameters.associations)
708
709    return client.begin_create(resource_group_name,
710                               profile_name,
711                               security_policy_name,
712                               SecurityPolicy(parameters=parameters))
713
714
715def create_afd_secret(client: SecretsOperations,
716                      resource_group_name,
717                      profile_name,
718                      secret_name,
719                      secret_source,
720                      secret_version: str = None,
721                      use_latest_version: bool = None):
722
723    if "/certificates/" not in secret_source.lower():
724        raise InvalidArgumentValueError('secret_source should be valid Azure key vault certificate ID.')
725
726    if secret_version is None and not use_latest_version:
727        raise InvalidArgumentValueError('Either specify secret_version or enable use_latest_version.')
728
729    # Only support CustomerCertificate for the moment
730    parameters = None
731    if use_latest_version:
732        parameters = CustomerCertificateParameters(
733            secret_source=ResourceReference(id=secret_source),
734            secret_version=None,
735            use_latest_version=True
736        )
737    else:
738        parameters = CustomerCertificateParameters(
739            secret_source=ResourceReference(id=f'{secret_source}/{secret_version}'),
740            secret_version=secret_version,
741            use_latest_version=False
742        )
743
744    return client.begin_create(resource_group_name,
745                               profile_name,
746                               secret_name,
747                               Secret(parameters=parameters))
748
749
750def update_afd_secret(client: SecretsOperations,
751                      resource_group_name,
752                      profile_name,
753                      secret_name,
754                      secret_source: str = None,
755                      secret_version: str = None,
756                      use_latest_version: bool = None):
757    existing = client.get(resource_group_name, profile_name, secret_name)
758
759    secret_source_id = secret_source if secret_source is not None else existing.parameters.secret_source.id
760    if existing.parameters.secret_version is not None and existing.parameters.secret_version in secret_source_id:
761        version_start = secret_source_id.lower().rindex(f'/{existing.parameters.secret_version}')
762        secret_source_id = secret_source_id[0:version_start]
763
764    secret_version = (secret_version if secret_version is not None
765                      else existing.parameters.secret_version)
766
767    use_latest_version = (use_latest_version if use_latest_version is not None
768                          else existing.parameters.use_latest_version)
769
770    return create_afd_secret(client,
771                             resource_group_name,
772                             profile_name,
773                             secret_name,
774                             secret_source_id,
775                             secret_version,
776                             use_latest_version)
777
778
779def create_afd_custom_domain(cmd,
780                             client: AFDCustomDomainsOperations,
781                             resource_group_name: str,
782                             profile_name: str,
783                             custom_domain_name: str,
784                             host_name: str,
785                             certificate_type: AfdCertificateType,
786                             minimum_tls_version: AfdMinimumTlsVersion,
787                             azure_dns_zone: str = None,
788                             secret: str = None,
789                             no_wait: bool = None):
790
791    if azure_dns_zone is not None and "/dnszones/" not in azure_dns_zone.lower():
792        raise InvalidArgumentValueError('azure_dns_zone should be valid Azure dns zone ID.')
793
794    subscription_id = get_subscription_id(cmd.cli_ctx)
795    if secret is not None and "/secrets/" not in secret.lower():
796        secret = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \
797                 f'/providers/Microsoft.Cdn/profiles/{profile_name}/secrets/{secret}'
798
799    tls_settings = AFDDomainHttpsParameters(certificate_type=certificate_type,
800                                            minimum_tls_version=minimum_tls_version,
801                                            secret=ResourceReference(id=secret) if secret is not None else None)
802
803    afd_domain = AFDDomain(host_name=host_name,
804                           tls_settings=tls_settings,
805                           azure_dns_zone=ResourceReference(id=azure_dns_zone) if azure_dns_zone is not None else None)
806
807    return sdk_no_wait(no_wait, client.begin_create, resource_group_name, profile_name, custom_domain_name, afd_domain)
808
809
810def update_afd_custom_domain(cmd,
811                             client: AFDCustomDomainsOperations,
812                             resource_group_name: str,
813                             profile_name: str,
814                             custom_domain_name: str,
815                             certificate_type: AfdCertificateType = None,
816                             minimum_tls_version: AfdMinimumTlsVersion = None,
817                             azure_dns_zone: str = None,
818                             secret: str = None):
819
820    if azure_dns_zone is not None and "/dnszones/" not in azure_dns_zone.lower():
821        raise InvalidArgumentValueError('azure_dns_zone should be valid Azure dns zone ID.')
822
823    subscription_id = get_subscription_id(cmd.cli_ctx)
824    if secret is not None and "/secrets/" not in secret.lower():
825        secret = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \
826                 f'/providers/Microsoft.Cdn/profiles/{profile_name}/secrets/{secret}'
827
828    existing = client.get(resource_group_name, profile_name, custom_domain_name)
829
830    tls_settings = AFDDomainHttpsParameters(certificate_type=certificate_type,
831                                            minimum_tls_version=minimum_tls_version,
832                                            secret=ResourceReference(id=secret) if secret is not None else None)
833
834    _update_mapper(existing.tls_settings, tls_settings, ["certificate_type", "minimum_tls_version", "secret"])
835
836    if certificate_type == AfdCertificateType.managed_certificate:
837        tls_settings.secret = None
838
839    afd_domain = AFDDomain(
840        host_name=existing.host_name,
841        tls_settings=tls_settings,
842        azure_dns_zone=ResourceReference(id=azure_dns_zone) if azure_dns_zone is not None else existing.azure_dns_zone)
843
844    return client.begin_create(resource_group_name,
845                               profile_name,
846                               custom_domain_name,
847                               afd_domain).result()
848
849# endregion
850