# -*- coding: utf-8 -*- #
# Copyright 2014 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command for creating forwarding rules."""

from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import ipaddress
import re

from googlecloudsdk.api_lib.compute import base_classes
from googlecloudsdk.api_lib.compute import constants
from googlecloudsdk.api_lib.compute import forwarding_rules_utils as utils
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.compute import flags as compute_flags
from googlecloudsdk.command_lib.compute.forwarding_rules import exceptions as fw_exceptions
from googlecloudsdk.command_lib.compute.forwarding_rules import flags
from googlecloudsdk.core import log
import six
from six.moves import range  # pylint: disable=redefined-builtin


def _Args(parser, support_traffic_disabled, support_all_protocol):
  """Add the flags to create a forwarding rule."""

  flags.AddCreateArgs(
      parser,
      include_psc_google_apis=True,
      include_target_service_attachment=True,
  )
  flags.AddIPProtocols(parser, support_all_protocol)
  flags.AddDescription(parser)
  flags.AddPortsAndPortRange(parser)
  flags.AddNetworkTier(parser, for_update=False)
  flags.AddAllowGlobalAccess(parser)
  flags.AddAllowPscGlobalAccess(parser)
  flags.AddSourceIpRanges(parser)
  flags.AddDisableAutomateDnsZone(parser)
  flags.AddIsMirroringCollector(parser)
  flags.AddServiceDirectoryRegistration(parser)

  if support_traffic_disabled:
    flags.AddTrafficDisabled(parser)

  parser.add_argument(
      '--service-label',
      help='(Only for Internal Load Balancing): '
      'https://cloud.google.com/load-balancing/docs/dns-names/\n'
      'The DNS label to use as the prefix of the fully qualified domain '
      'name for this forwarding rule. The full name will be internally '
      'generated and output as dnsName. If this field is not specified, '
      'no DNS record will be generated and no DNS name will be output. '
      'You cannot use the `--service-label` flag  if the forwarding rule '
      'references an internal IP address that has the '
      '`--purpose=SHARED_LOADBALANCER_VIP` flag set.')
  flags.AddAddressesAndIPVersions(parser)
  forwarding_rule_arg = flags.ForwardingRuleArgument()
  forwarding_rule_arg.AddArgument(parser, operation_type='create')
  parser.display_info.AddCacheUpdater(flags.ForwardingRulesCompleter)
  return forwarding_rule_arg


class CreateHelper(object):
  """Helper class to create a forwarding rule."""

  FORWARDING_RULE_ARG = None

  def __init__(
      self,
      holder,
      support_traffic_disabled,
      support_all_protocol,
      support_sd_registration_for_regional,
  ):
    self._holder = holder
    self._support_traffic_disabled = support_traffic_disabled
    self._support_all_protocol = support_all_protocol
    self._support_sd_registration_for_regional = (
        support_sd_registration_for_regional
    )

  @classmethod
  def Args(cls, parser, support_traffic_disabled, support_all_protocol):
    """Inits the class args for supported features."""
    cls.FORWARDING_RULE_ARG = _Args(
        parser, support_traffic_disabled, support_all_protocol
    )

  def ConstructProtocol(self, messages, args):
    if args.ip_protocol:
      return messages.ForwardingRule.IPProtocolValueValuesEnum(args.ip_protocol)
    else:
      return

  def Run(self, args):
    """Issues requests necessary to create Forwarding Rules."""
    client = self._holder.client

    forwarding_rule_ref = self.FORWARDING_RULE_ARG.ResolveAsResource(
        args,
        self._holder.resources,
        scope_lister=compute_flags.GetDefaultScopeLister(client))

    if forwarding_rule_ref.Collection() == 'compute.globalForwardingRules':
      requests = self._CreateGlobalRequests(client, self._holder.resources,
                                            args, forwarding_rule_ref)
    elif forwarding_rule_ref.Collection() == 'compute.forwardingRules':
      requests = self._CreateRegionalRequests(client, self._holder.resources,
                                              args, forwarding_rule_ref)

    return client.MakeRequests(requests)

  def _CreateGlobalRequests(self, client, resources, args, forwarding_rule_ref):
    """Create a globally scoped request."""

    is_psc_google_apis = (
        hasattr(args, 'target_google_apis_bundle')
        and args.target_google_apis_bundle
    )

    sd_registration = None
    if hasattr(args, 'service_directory_registration'
              ) and args.service_directory_registration:
      if not is_psc_google_apis:
        raise exceptions.InvalidArgumentException(
            '--service-directory-registration',
            'Can only be specified for regional forwarding rules or Private Service Connect forwarding rules targeting a Google APIs bundle.'
        )
      # Parse projects/../locations/..
      match = re.match(
          r'^projects/([^/]+)/locations/([^/]+)(?:/namespaces/([^/]+))?$',
          args.service_directory_registration)
      if not match:
        raise exceptions.InvalidArgumentException(
            '--service-directory-registration',
            'Must be of the form projects/PROJECT/locations/REGION or projects/PROJECT/locations/REGION/namespaces/NAMESPACE'
        )
      project = match.group(1)
      region = match.group(2)
      namespace = match.group(3)

      if project != forwarding_rule_ref.project:
        raise exceptions.InvalidArgumentException(
            '--service-directory-registration',
            'Must be in the same project as the forwarding rule.')

      sd_registration = client.messages.ForwardingRuleServiceDirectoryRegistration(
          serviceDirectoryRegion=region, namespace=namespace)

    ports_all_specified, range_list = _ExtractPortsAndAll(args.ports)
    port_range = _MakeSingleUnifiedPortRange(args.port_range, range_list)
    # All global forwarding rules must use EXTERNAL or INTERNAL_SELF_MANAGED
    # schemes presently.
    load_balancing_scheme = _GetLoadBalancingScheme(args, client.messages,
                                                    is_psc_google_apis)
    if (load_balancing_scheme == client.messages.ForwardingRule
        .LoadBalancingSchemeValueValuesEnum.INTERNAL):
      raise fw_exceptions.ArgumentError(
          'You cannot specify internal [--load-balancing-scheme] for a global '
          'forwarding rule.')

    if (load_balancing_scheme == client.messages.ForwardingRule
        .LoadBalancingSchemeValueValuesEnum.INTERNAL_SELF_MANAGED):
      if (not args.target_http_proxy and not args.target_https_proxy and
          not args.target_grpc_proxy and not args.target_tcp_proxy):
        target_error_message_with_tcp = (
            'You must specify either [--target-http-proxy], '
            '[--target-https-proxy], [--target-grpc-proxy] '
            'or [--target-tcp-proxy] for an '
            'INTERNAL_SELF_MANAGED [--load-balancing-scheme].')
        raise fw_exceptions.ArgumentError(target_error_message_with_tcp)

      if args.subnet:
        raise fw_exceptions.ArgumentError(
            'You cannot specify [--subnet] for an INTERNAL_SELF_MANAGED '
            '[--load-balancing-scheme].')

      if not args.address:
        raise fw_exceptions.ArgumentError(
            'You must specify [--address] for an INTERNAL_SELF_MANAGED '
            '[--load-balancing-scheme]')

    if is_psc_google_apis:
      rule_name = forwarding_rule_ref.Name()
      if len(rule_name) > 20 or rule_name[0].isdigit(
      ) or not rule_name.isalnum():
        raise fw_exceptions.ArgumentError(
            'A forwarding rule to Google APIs must have a name that is between '
            ' 1-20 characters long, alphanumeric, starting with a letter.')

      if port_range:
        raise exceptions.InvalidArgumentException(
            '--ports',
            '[--ports] is not allowed for PSC-GoogleApis forwarding rules.')
      if load_balancing_scheme:
        raise exceptions.InvalidArgumentException(
            '--load-balancing-scheme',
            'The --load-balancing-scheme flag is not allowed for PSC-GoogleApis'
            ' forwarding rules.')

      if args.target_google_apis_bundle in flags.PSC_GOOGLE_APIS_BUNDLES:
        target_as_str = args.target_google_apis_bundle
      else:
        bundles_list = ', '.join(flags.PSC_GOOGLE_APIS_BUNDLES)
        raise exceptions.InvalidArgumentException(
            '--target-google-apis-bundle',
            'The valid values for target-google-apis-bundle are: ' +
            bundles_list)
    else:
      # L7XLB in Premium Tier.
      target_ref = utils.GetGlobalTarget(resources, args)
      target_as_str = target_ref.SelfLink()

      if ports_all_specified:
        raise exceptions.InvalidArgumentException(
            '--ports',
            '[--ports] cannot be set to ALL for global forwarding rules.')
      if not port_range:
        raise exceptions.InvalidArgumentException(
            '--ports', '[--ports] is required for global forwarding rules.')

    protocol = self.ConstructProtocol(client.messages, args)

    address = self._ResolveAddress(resources, args,
                                   compute_flags.compute_scope.ScopeEnum.GLOBAL,
                                   forwarding_rule_ref)
    forwarding_rule = client.messages.ForwardingRule(
        description=args.description,
        name=forwarding_rule_ref.Name(),
        IPAddress=address,
        IPProtocol=protocol,
        portRange=port_range,
        target=target_as_str,
        networkTier=_ConstructNetworkTier(client.messages, args),
        loadBalancingScheme=load_balancing_scheme)

    self._ProcessCommonArgs(client, resources, args, forwarding_rule_ref,
                            forwarding_rule)
    if sd_registration:
      forwarding_rule.serviceDirectoryRegistrations.append(sd_registration)

    if args.IsSpecified('allow_global_access'):
      forwarding_rule.allowGlobalAccess = args.allow_global_access

    request = client.messages.ComputeGlobalForwardingRulesInsertRequest(
        forwardingRule=forwarding_rule, project=forwarding_rule_ref.project)

    return [(client.apitools_client.globalForwardingRules, 'Insert', request)]

  def _CreateRegionalRequests(self, client, resources, args,
                              forwarding_rule_ref):
    """Create a regionally scoped request."""
    is_psc_ilb = (
        hasattr(args, 'target_service_attachment')
        and args.target_service_attachment
    )

    target_ref, region_ref = utils.GetRegionalTarget(
        client, resources, args, forwarding_rule_ref
    )

    if not args.region and region_ref:
      args.region = region_ref

    protocol = self.ConstructProtocol(client.messages, args)

    address = self._ResolveAddress(resources, args,
                                   compute_flags.compute_scope.ScopeEnum.REGION,
                                   forwarding_rule_ref)

    load_balancing_scheme = _GetLoadBalancingScheme(args, client.messages,
                                                    is_psc_ilb)

    if is_psc_ilb and load_balancing_scheme:
      raise exceptions.InvalidArgumentException(
          '--load-balancing-scheme',
          'The --load-balancing-scheme flag is not allowed for PSC-ILB '
          'forwarding rules.')

    if (load_balancing_scheme == client.messages.ForwardingRule
        .LoadBalancingSchemeValueValuesEnum.INTERNAL):
      if args.port_range:
        raise fw_exceptions.ArgumentError(
            'You cannot specify [--port-range] for a forwarding rule '
            'whose [--load-balancing-scheme] is internal, '
            'please use [--ports] flag instead.')

    if (load_balancing_scheme == client.messages.ForwardingRule
        .LoadBalancingSchemeValueValuesEnum.INTERNAL_SELF_MANAGED):
      raise fw_exceptions.ArgumentError(
          'You cannot specify an INTERNAL_SELF_MANAGED '
          '[--load-balancing-scheme] for a regional forwarding rule.')

    forwarding_rule = client.messages.ForwardingRule(
        description=args.description,
        name=forwarding_rule_ref.Name(),
        IPAddress=address,
        IPProtocol=protocol,
        networkTier=_ConstructNetworkTier(client.messages, args),
        loadBalancingScheme=load_balancing_scheme)
    if args.source_ip_ranges:
      forwarding_rule.sourceIpRanges = args.source_ip_ranges

    self._ProcessCommonArgs(client, resources, args, forwarding_rule_ref,
                            forwarding_rule)

    ports_all_specified, range_list = _ExtractPortsAndAll(args.ports)

    if target_ref.Collection() == 'compute.regionBackendServices':
      # A FR pointing to a BES has no target attribute.
      forwarding_rule.backendService = target_ref.SelfLink()
      forwarding_rule.target = None
    else:
      # A FR pointing to anything not a BES has a target attribute.
      forwarding_rule.backendService = None
      forwarding_rule.target = target_ref.SelfLink()

    if ((target_ref.Collection() == 'compute.regionBackendServices' or
         target_ref.Collection() == 'compute.targetInstances') and
        args.load_balancing_scheme == 'INTERNAL'):
      # This is for L4ILB and internal protocol forwarding.
      # API fields allPorts, ports, and portRange are mutually exclusive.
      # API field portRange is not valid for this case.
      # Use of L3_DEFAULT implies all ports even if allPorts is unset.
      if ports_all_specified:
        forwarding_rule.allPorts = True
      elif range_list:
        forwarding_rule.ports = [
            six.text_type(p) for p in _GetPortList(range_list)
        ]
    elif ((target_ref.Collection() == 'compute.regionTargetHttpProxies' or
           target_ref.Collection() == 'compute.regionTargetHttpsProxies') and
          args.load_balancing_scheme == 'INTERNAL'):
      # This is a legacy configuration for L7ILB.
      forwarding_rule.ports = [
          six.text_type(p) for p in _GetPortList(range_list)
      ]
    elif args.load_balancing_scheme == 'INTERNAL':
      # There are currently no other valid combinations of targets with scheme
      # internal. With scheme internal, targets must presently be a regional
      # backend service (L4ILB) or a target instance (protocol forwarding).
      raise exceptions.InvalidArgumentException(
          '--load-balancing-scheme',
          'Only target instances and backend services should be specified as '
          'a target for internal load balancing.')
    elif args.load_balancing_scheme == 'INTERNAL_MANAGED':
      # This is L7ILB.
      forwarding_rule.portRange = _MakeSingleUnifiedPortRange(
          args.port_range, range_list)
    elif args.load_balancing_scheme == 'EXTERNAL_MANAGED':
      # This is regional L7XLB.
      forwarding_rule.portRange = _MakeSingleUnifiedPortRange(
          args.port_range, range_list)
    elif ((target_ref.Collection() == 'compute.regionBackendServices') and
          ((args.load_balancing_scheme == 'EXTERNAL') or
           (not args.load_balancing_scheme))):
      # This is NetLB using a backend service. Scheme is either explicitly
      # EXTERNAL or not supplied (EXTERNAL is the default scheme).
      # API fields allPorts, ports, and portRange are mutually exclusive.
      # All three API fields are valid for this case.
      # Use of L3_DEFAULT implies all ports even if allPorts is unset.
      if ports_all_specified:
        forwarding_rule.allPorts = True
      elif range_list:
        if len(range_list) > 1:
          # More than one port, potentially discontiguous, from --ports= flag.
          forwarding_rule.ports = [
              six.text_type(p) for p in _GetPortList(range_list)
          ]
        else:
          # Exactly one value from --ports= flag. Might be a single port (80);
          # might be a range (80-90). Since it might be a range, the portRange
          # API attribute is more appropriate.
          forwarding_rule.portRange = six.text_type(range_list[0])
      elif args.port_range:
        forwarding_rule.portRange = _MakeSingleUnifiedPortRange(
            args.port_range, range_list)
    elif ((target_ref.Collection() == 'compute.targetPool' or
           target_ref.Collection() == 'compute.targetInstances') and
          ((args.load_balancing_scheme == 'EXTERNAL') or
           (not args.load_balancing_scheme))):
      # This is NetLB using a target pool or external protocol forwarding.
      # Scheme is either explicitly EXTERNAL or not supplied (EXTERNAL is the
      # default scheme).
      # API fields allPorts, ports, and portRange are mutually exclusive.
      # API field ports is not valid for this case.
      # Use of L3_DEFAULT implies all ports by definition.
      if ports_all_specified:
        forwarding_rule.allPorts = True
      else:
        forwarding_rule.portRange = _MakeSingleUnifiedPortRange(
            args.port_range, range_list)
    else:
      # All other regional forwarding rules with load balancing scheme EXTERNAL.
      forwarding_rule.portRange = _MakeSingleUnifiedPortRange(
          args.port_range, range_list)

    if self._support_traffic_disabled and args.IsSpecified('traffic_disabled'):
      forwarding_rule.trafficDisabled = args.traffic_disabled

    if hasattr(args, 'service_label'):
      forwarding_rule.serviceLabel = args.service_label

    if args.IsSpecified('allow_global_access'):
      forwarding_rule.allowGlobalAccess = args.allow_global_access

    if args.IsSpecified('allow_psc_global_access'):
      forwarding_rule.allowPscGlobalAccess = args.allow_psc_global_access

    if args.IsSpecified('ip_collection'):
      forwarding_rule.ipCollection = flags.IP_COLLECTION_ARG.ResolveAsResource(
          args, resources).SelfLink()

    if args.IsSpecified('disable_automate_dns_zone'):
      forwarding_rule.noAutomateDnsZone = args.disable_automate_dns_zone

    if hasattr(args, 'is_mirroring_collector'):
      forwarding_rule.isMirroringCollector = args.is_mirroring_collector

    if hasattr(args, 'service_directory_registration'
              ) and args.service_directory_registration:
      if is_psc_ilb:
        # Parse projects/../locations/../namespaces/..
        match = re.match(
            r'^projects/([^/]+)/locations/([^/]+)/namespaces/([^/]+)$',
            args.service_directory_registration)
        if not match:
          raise exceptions.InvalidArgumentException(
              '--service-directory-registration',
              'If set, must be of the form projects/PROJECT/locations/REGION/namespaces/NAMESPACE'
          )
        project = match.group(1)
        region = match.group(2)

        if project != forwarding_rule_ref.project or region != forwarding_rule_ref.region:
          raise exceptions.InvalidArgumentException(
              '--service-directory-registration',
              'Service Directory registration must be in the same project and region as the forwarding rule.'
          )

        sd_registration = client.messages.ForwardingRuleServiceDirectoryRegistration(
            namespace=match.group(3))
        forwarding_rule.serviceDirectoryRegistrations.append(sd_registration)
      else:
        if not self._support_sd_registration_for_regional:
          raise exceptions.InvalidArgumentException(
              '--service-directory-registration',
              """flag is available in one or more alternate release tracks. Try:

  gcloud alpha compute forwarding-rules create --service-directory-registration
  gcloud beta compute forwarding-rules create --service-directory-registration"""
          )
        # Parse projects/../locations/../namespaces/../services/..
        match = re.match(
            r'^projects/([^/]+)/locations/([^/]+)/namespaces/([^/]+)/services/([^/]+)$',
            args.service_directory_registration)
        if not match:
          raise exceptions.InvalidArgumentException(
              '--service-directory-registration',
              'Must be of the form projects/PROJECT/locations/REGION/namespaces/NAMESPACE/services/SERVICE'
          )
        project = match.group(1)
        region = match.group(2)

        if project != forwarding_rule_ref.project or region != forwarding_rule_ref.region:
          raise exceptions.InvalidArgumentException(
              '--service-directory-registration',
              'Service Directory registration must be in the same project and region as the forwarding rule.'
          )

        sd_registration = client.messages.ForwardingRuleServiceDirectoryRegistration(
            namespace=match.group(3), service=match.group(4))
        forwarding_rule.serviceDirectoryRegistrations.append(sd_registration)

    request = client.messages.ComputeForwardingRulesInsertRequest(
        forwardingRule=forwarding_rule,
        project=forwarding_rule_ref.project,
        region=forwarding_rule_ref.region)

    return [(client.apitools_client.forwardingRules, 'Insert', request)]

  def _ResolveAddress(self, resources, args, scope, forwarding_rule_ref):
    """Resolve address resource."""

    # Address takes either an ip address or an address resource. If parsing as
    # an IP address fails, then we resolve as a resource.
    address = args.address
    if address is not None:
      try:
        # ipaddress only allows unicode input
        ipaddress.ip_network(six.text_type(args.address))
      except ValueError:
        # TODO(b/37086838): Make sure global/region settings are inherited by
        # address resource.
        if scope == compute_flags.compute_scope.ScopeEnum.REGION:
          if not args.global_address and not args.address_region:
            if forwarding_rule_ref.Collection() == 'compute.forwardingRules':
              args.address_region = forwarding_rule_ref.region
        address_ref = flags.AddressArg().ResolveAsResource(
            args, resources, default_scope=scope)
        address = address_ref.SelfLink()

    return address

  def _ProcessCommonArgs(self, client, resources, args, forwarding_rule_ref,
                         forwarding_rule):
    """Processes common arguments for global and regional commands.

    Args:
      client: The client used by gcloud.
      resources: The resource parser.
      args: The arguments passed to the gcloud command.
      forwarding_rule_ref: The forwarding rule reference.
      forwarding_rule: The forwarding rule to set properties on.
    """

    if args.ip_version:
      forwarding_rule.ipVersion = (
          client.messages.ForwardingRule.IpVersionValueValuesEnum(
              args.ip_version))
    if args.network:
      forwarding_rule.network = flags.NetworkArg().ResolveAsResource(
          args, resources).SelfLink()
    if args.subnet:
      if (not args.subnet_region and
          forwarding_rule_ref.Collection() == 'compute.forwardingRules'):
        args.subnet_region = forwarding_rule_ref.region
      forwarding_rule.subnetwork = flags.SUBNET_ARG.ResolveAsResource(
          args, resources).SelfLink()


@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.UniverseCompatible
class Create(base.CreateCommand):
  """Create a forwarding rule to direct network traffic to a load balancer."""
  _support_traffic_disabled = False
  _support_all_protocol = False
  _support_sd_registration_for_regional = False

  @classmethod
  def Args(cls, parser):
    CreateHelper.Args(
        parser, cls._support_traffic_disabled, cls._support_all_protocol
        )

  def Run(self, args):
    holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
    return CreateHelper(
        holder,
        self._support_traffic_disabled,
        self._support_all_protocol,
        self._support_sd_registration_for_regional,
    ).Run(args)


@base.ReleaseTracks(base.ReleaseTrack.BETA)
class CreateBeta(Create):
  """Create a forwarding rule to direct network traffic to a load balancer."""
  _support_traffic_disabled = False
  _support_all_protocol = False
  _support_sd_registration_for_regional = True


@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class CreateAlpha(CreateBeta):
  """Create a forwarding rule to direct network traffic to a load balancer."""
  _support_traffic_disabled = True
  _support_all_protocol = True
  _support_sd_registration_for_regional = True


Create.detailed_help = {
    'DESCRIPTION': ("""
*{{command}}* is used to create a forwarding rule. {overview}

When creating a forwarding rule, exactly one of  ``--target-instance'',
``--target-pool'', ``--target-http-proxy'', ``--target-https-proxy'',
``--target-grpc-proxy'', ``--target-ssl-proxy'', ``--target-tcp-proxy'',
``--target-vpn-gateway'', ``--backend-service'' or ``--target-google-apis-bundle''
must be specified.""".format(overview=flags.FORWARDING_RULES_OVERVIEW)),
    'EXAMPLES':
        """
    To create a global forwarding rule that will forward all traffic on port
    8080 for IP address ADDRESS to a target http proxy PROXY, run:

      $ {command} RULE_NAME --global --target-http-proxy=PROXY --ports=8080 --address=ADDRESS

    To create a regional forwarding rule for the subnet SUBNET_NAME on the
    default network that will forward all traffic on ports 80-82 to a
    backend service SERVICE_NAME, run:

      $ {command} RULE_NAME --load-balancing-scheme=INTERNAL --backend-service=SERVICE_NAME --subnet=SUBNET_NAME --network=default --region=REGION --ports=80-82
"""
}

CreateBeta.detailed_help = Create.detailed_help
CreateAlpha.detailed_help = CreateBeta.detailed_help


def _UnifyPortRangeFromListOfRanges(ports_range_list):
  """Return a single port range by combining a list of port ranges."""
  if not ports_range_list:
    return None, None
  ports = sorted(ports_range_list)
  combined_port_range = ports.pop(0)
  for port_range in ports_range_list:
    try:
      combined_port_range = combined_port_range.Combine(port_range)
    except arg_parsers.Error:
      raise exceptions.InvalidArgumentException(
          '--ports', 'Must specify consecutive ports at this time.')
  return combined_port_range


def _ExtractPortsAndAll(ports_with_all):
  if ports_with_all:
    return ports_with_all.all_specified, ports_with_all.ranges
  else:
    return False, []


def _MakeSingleUnifiedPortRange(arg_port_range, range_list_from_arg_ports):
  """Reconciles the deprecated --port-range arg with ranges from --ports arg."""
  if arg_port_range:
    log.warning(
        'The --port-range flag is deprecated. Use equivalent --ports=%s'
        ' flag.', arg_port_range)
    return six.text_type(arg_port_range)
  elif range_list_from_arg_ports:
    range_list = _UnifyPortRangeFromListOfRanges(range_list_from_arg_ports)
    return six.text_type(range_list) if range_list else None


def _GetPortList(range_list):
  """Creates list of singleton port numbers from list of ports and ranges."""
  ports = []
  for port_range in range_list:
    ports.extend(list(range(port_range.start, port_range.end + 1)))
  return sorted(ports)


def _GetLoadBalancingScheme(args, messages, is_psc):
  """Get load balancing scheme."""
  if not args.load_balancing_scheme:
    # The default is EXTERNAL for non-PSC forwarding rules.
    return None if is_psc else messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.EXTERNAL
  if args.load_balancing_scheme == 'INTERNAL':
    return messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.INTERNAL
  elif args.load_balancing_scheme == 'EXTERNAL':
    return messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.EXTERNAL
  elif args.load_balancing_scheme == 'EXTERNAL_MANAGED':
    return messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.EXTERNAL_MANAGED
  elif args.load_balancing_scheme == 'INTERNAL_SELF_MANAGED':
    return (messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum
            .INTERNAL_SELF_MANAGED)
  elif args.load_balancing_scheme == 'INTERNAL_MANAGED':
    return (messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum
            .INTERNAL_MANAGED)
  return None


def _ConstructNetworkTier(messages, args):
  """Get network tier."""
  if args.network_tier:
    network_tier = args.network_tier.upper()
    if network_tier in constants.NETWORK_TIER_CHOICES_FOR_INSTANCE:
      return messages.ForwardingRule.NetworkTierValueValuesEnum(
          args.network_tier)
    else:
      raise exceptions.InvalidArgumentException(
          '--network-tier',
          'Invalid network tier [{tier}]'.format(tier=network_tier))
  else:
    return
