@@ -319,10 +319,75 @@ def _validate_ip_address_existence(cmd, namespace):
319319 scm_site = namespace .scm_site
320320 configs = _generic_site_operation (cmd .cli_ctx , resource_group_name , name , 'get_configuration' , slot )
321321 access_rules = configs .scm_ip_security_restrictions if scm_site else configs .ip_security_restrictions
322- ip_exists = [x .ip_address == namespace .ip_address for x in access_rules ]
323- if True in ip_exists :
324- raise ArgumentUsageError ('IP address: ' + namespace .ip_address + ' already exists. '
325- 'Cannot add duplicate IP address values.' )
322+ new_headers = _normalize_http_headers (getattr (namespace , 'http_headers' , None ))
323+ new_ip_set = _normalize_ip_address_list (namespace .ip_address )
324+ for rule in access_rules or []:
325+ if _normalize_ip_address_list (rule .ip_address ) != new_ip_set :
326+ continue
327+ if _normalize_http_headers (rule .headers ) == new_headers :
328+ if not new_headers :
329+ raise ArgumentUsageError (
330+ "An access-restriction rule with IP address '{}' already exists. Cannot add a "
331+ "duplicate rule. Use a different IP address, or add a --http-header filter to "
332+ "create an additional rule." .format (namespace .ip_address ))
333+ raise ArgumentUsageError (
334+ "An access-restriction rule with IP address '{}' and the same HTTP header filter "
335+ "already exists. Cannot add a duplicate rule. Use a different IP address or vary "
336+ "the --http-header values to create an additional rule." .format (namespace .ip_address ))
337+
338+
339+ def _normalize_ip_address_list (ip_address ):
340+ """Return a frozenset of canonical CIDR strings parsed from a comma-separated input.
341+
342+ The CLI accepts up to 8 comma-separated CIDRs in a single rule's ``--ip-address``. ARM stores
343+ them in the same comma-separated form on the rule's ``ip_address`` attribute. Two rules
344+ represent the same source set regardless of the order in which the CIDRs appear, so duplicate
345+ detection should compare unordered sets. Returns ``frozenset()`` for missing/empty input.
346+ """
347+ if not ip_address :
348+ return frozenset ()
349+ return frozenset (part .strip () for part in ip_address .split (',' ) if part .strip ())
350+
351+
352+ def _normalize_http_headers (headers ):
353+ """Normalize an http-header collection for duplicate-rule comparison.
354+
355+ Accepts either the CLI input form (list of "name=value" strings, as supplied via --http-header)
356+ or the SDK/ARM form (dict mapping header name to a list of values). Returns a dict whose keys
357+ are lowercased header names and whose values are frozensets of value strings, so that order of
358+ values within a single header (which ARM treats as a set) does not affect equality. None and
359+ an empty collection are both normalized to an empty dict so that a rule with no headers
360+ compares equal to itself regardless of representation.
361+ """
362+ if not headers :
363+ return {}
364+ result = {}
365+ if isinstance (headers , dict ):
366+ for header_name , values in headers .items ():
367+ if header_name is None :
368+ continue
369+ normalized_name = header_name .strip ().lower ()
370+ if values is None :
371+ continue
372+ if isinstance (values , str ):
373+ values = [values ]
374+ value_set = frozenset (v for v in values if v )
375+ if value_set :
376+ result [normalized_name ] = value_set
377+ return result
378+ # CLI input form: list of "name=value" strings.
379+ for header_str in headers :
380+ if header_str is None or '=' not in header_str :
381+ continue
382+ header_name , _ , header_value = header_str .partition ('=' )
383+ normalized_name = header_name .strip ().lower ()
384+ normalized_value = header_value .strip ()
385+ if not normalized_name or not normalized_value :
386+ continue
387+ existing = set (result .get (normalized_name , frozenset ()))
388+ existing .add (normalized_value )
389+ result [normalized_name ] = frozenset (existing )
390+ return result
326391
327392
328393def validate_service_tag (cmd , namespace ):
@@ -369,10 +434,21 @@ def _validate_service_tag_existence(cmd, namespace):
369434 input_tag_value = namespace .service_tag .replace (' ' , '' )
370435 configs = _generic_site_operation (cmd .cli_ctx , resource_group_name , name , 'get_configuration' , slot )
371436 access_rules = configs .scm_ip_security_restrictions if scm_site else configs .ip_security_restrictions
372- for rule in access_rules :
373- if rule .ip_address and rule .ip_address .lower () == input_tag_value .lower ():
374- raise ArgumentUsageError ('Service Tag: ' + namespace .service_tag + ' already exists. '
375- 'Cannot add duplicate Service Tag values.' )
437+ new_headers = _normalize_http_headers (getattr (namespace , 'http_headers' , None ))
438+ for rule in access_rules or []:
439+ if not rule .ip_address or rule .ip_address .lower () != input_tag_value .lower ():
440+ continue
441+ if _normalize_http_headers (rule .headers ) == new_headers :
442+ if not new_headers :
443+ raise ArgumentUsageError (
444+ "A service-tag access-restriction rule with value '{}' already exists. Cannot "
445+ "add a duplicate rule. Use a different service tag, or add a --http-header "
446+ "filter to create an additional rule." .format (namespace .service_tag ))
447+ raise ArgumentUsageError (
448+ "A service-tag access-restriction rule with value '{}' and the same HTTP header "
449+ "filter already exists. Cannot add a duplicate rule. Use a different service tag "
450+ "or vary the --http-header values to create an additional rule." .format (
451+ namespace .service_tag ))
376452
377453
378454def validate_public_cloud (cmd ):
0 commit comments