# -*- 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 firewall rules."""


from googlecloudsdk.api_lib.compute import base_classes
from googlecloudsdk.api_lib.compute import firewalls_utils
from googlecloudsdk.api_lib.compute import utils as compute_api
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.compute import resource_manager_tags_utils
from googlecloudsdk.command_lib.compute.firewall_rules import flags
from googlecloudsdk.command_lib.compute.networks import flags as network_flags
from googlecloudsdk.core.console import progress_tracker
import six


@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.PREVIEW)
@base.UniverseCompatible
class Create(base.CreateCommand):
  """Create a Compute Engine firewall rule."""

  FIREWALL_RULE_ARG = None
  NETWORK_ARG = None

  @classmethod
  def Args(cls, parser):
    messages = apis.GetMessagesModule('compute',
                                      compute_api.COMPUTE_GA_API_VERSION)
    parser.display_info.AddFormat(flags.DEFAULT_LIST_FORMAT)
    cls.FIREWALL_RULE_ARG = flags.FirewallRuleArgument()
    cls.FIREWALL_RULE_ARG.AddArgument(parser, operation_type='create')
    cls.NETWORK_ARG = network_flags.NetworkArgumentForOtherResource(
        'The network to which this rule is attached.', required=False)
    firewalls_utils.AddCommonArgs(
        parser,
        for_update=False,
        with_egress_support=True,
        with_service_account=True)
    firewalls_utils.AddArgsForServiceAccount(parser, for_update=False)
    flags.AddEnableLogging(parser)
    flags.AddLoggingMetadata(parser, messages)
    parser.display_info.AddCacheUpdater(flags.FirewallsCompleter)

  def _CreateFirewall(self, holder, args):
    client = holder.client

    if args.rules and args.allow:
      raise firewalls_utils.ArgumentValidationError(
          'Can NOT specify --rules and --allow in the same request.')

    if bool(args.action) ^ bool(args.rules):
      raise firewalls_utils.ArgumentValidationError(
          'Must specify --rules with --action.')

    allowed = firewalls_utils.ParseRules(args.allow, client.messages,
                                         firewalls_utils.ActionType.ALLOW)

    network_ref = self.NETWORK_ARG.ResolveAsResource(args, holder.resources)
    firewall_ref = self.FIREWALL_RULE_ARG.ResolveAsResource(
        args, holder.resources)

    firewall = client.messages.Firewall(
        allowed=allowed,
        name=firewall_ref.Name(),
        description=args.description,
        network=network_ref.SelfLink(),
        sourceRanges=args.source_ranges,
        sourceTags=args.source_tags,
        targetTags=args.target_tags)

    if args.disabled is not None:
      firewall.disabled = args.disabled

    firewall.direction = None
    if args.direction and args.direction in ['EGRESS', 'OUT']:
      firewall.direction = (
          client.messages.Firewall.DirectionValueValuesEnum.EGRESS)
    else:
      firewall.direction = (
          client.messages.Firewall.DirectionValueValuesEnum.INGRESS)

    firewall.priority = args.priority
    firewall.destinationRanges = args.destination_ranges

    allowed = []
    denied = []
    if not args.action:
      allowed = firewalls_utils.ParseRules(
          args.allow, client.messages, firewalls_utils.ActionType.ALLOW)
    elif args.action == 'ALLOW':
      allowed = firewalls_utils.ParseRules(
          args.rules, client.messages, firewalls_utils.ActionType.ALLOW)
    elif args.action == 'DENY':
      denied = firewalls_utils.ParseRules(
          args.rules, client.messages, firewalls_utils.ActionType.DENY)
    firewall.allowed = allowed
    firewall.denied = denied

    firewall.sourceServiceAccounts = args.source_service_accounts
    firewall.targetServiceAccounts = args.target_service_accounts

    if args.IsSpecified('logging_metadata') and not args.enable_logging:
      raise exceptions.InvalidArgumentException(
          '--logging-metadata',
          'cannot toggle logging metadata if logging is not enabled.',
      )

    if args.IsSpecified('enable_logging'):
      log_config = client.messages.FirewallLogConfig(enable=args.enable_logging)
      if args.IsSpecified('logging_metadata'):
        log_config.metadata = flags.GetLoggingMetadataArg(
            client.messages
        ).GetEnumForChoice(args.logging_metadata)
      firewall.logConfig = log_config

    if args.IsSpecified(
        'resource_manager_tags'
    ):
      firewall.params = self._CreateFirewallParams(
          client.messages, args.resource_manager_tags
      )

    return firewall, firewall_ref.project

  def _CreateFirewallParams(self, messages, resource_manager_tags):
    resource_manager_tags_map = (
        resource_manager_tags_utils.GetResourceManagerTags(
            resource_manager_tags
        )
    )
    params = messages.FirewallParams
    additional_properties = [
        params.ResourceManagerTagsValue.AdditionalProperty(key=key, value=value)
        for key, value in sorted(six.iteritems(resource_manager_tags_map))
    ]
    return params(
        resourceManagerTags=params.ResourceManagerTagsValue(
            additionalProperties=additional_properties
        )
    )

  def Run(self, args):
    """Issues requests necessary for adding firewall rules."""
    holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
    client = holder.client

    firewall, project = self._CreateFirewall(holder, args)
    request = client.messages.ComputeFirewallsInsertRequest(
        firewall=firewall, project=project)
    with progress_tracker.ProgressTracker(
        'Creating firewall',
        autotick=False
    ) as tracker:
      return client.MakeRequests([(client.apitools_client.firewalls, 'Insert',
                                   request)], progress_tracker=tracker)


@base.ReleaseTracks(base.ReleaseTrack.BETA)
class BetaCreate(Create):
  """Create a Compute Engine firewall rule."""

  @classmethod
  def Args(cls, parser):
    messages = apis.GetMessagesModule('compute',
                                      compute_api.COMPUTE_BETA_API_VERSION)
    parser.display_info.AddFormat(flags.DEFAULT_LIST_FORMAT)
    cls.FIREWALL_RULE_ARG = flags.FirewallRuleArgument()
    cls.FIREWALL_RULE_ARG.AddArgument(parser, operation_type='create')
    cls.NETWORK_ARG = network_flags.NetworkArgumentForOtherResource(
        'The network to which this rule is attached.', required=False)
    firewalls_utils.AddCommonArgs(
        parser,
        for_update=False,
        with_egress_support=True,
        with_service_account=True)
    firewalls_utils.AddArgsForServiceAccount(parser, for_update=False)
    flags.AddEnableLogging(parser)
    flags.AddLoggingMetadata(parser, messages)


@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class AlphaCreate(BetaCreate):
  """Create a Compute Engine firewall rule."""

  @classmethod
  def Args(cls, parser):
    messages = apis.GetMessagesModule('compute',
                                      compute_api.COMPUTE_ALPHA_API_VERSION)
    parser.display_info.AddFormat(flags.DEFAULT_LIST_FORMAT)
    cls.FIREWALL_RULE_ARG = flags.FirewallRuleArgument()
    cls.FIREWALL_RULE_ARG.AddArgument(parser, operation_type='create')
    cls.NETWORK_ARG = network_flags.NetworkArgumentForOtherResource(
        'The network to which this rule is attached.', required=False)
    firewalls_utils.AddCommonArgs(
        parser,
        for_update=False,
        with_egress_support=True,
        with_service_account=True)
    firewalls_utils.AddArgsForServiceAccount(parser, for_update=False)
    flags.AddEnableLogging(parser)
    flags.AddLoggingMetadata(parser, messages)


Create.detailed_help = {
    'brief': 'Create a Compute Engine firewall rule.',
    'DESCRIPTION': """
*{command}* is used to create firewall rules to allow/deny
incoming/outgoing traffic.
""",
    'EXAMPLES': """
To create a firewall rule allowing incoming TCP traffic on port 8080, run:

  $ {command} example-service --allow=tcp:8080
      --description="Allow incoming traffic on TCP port 8080" --direction=INGRESS

To create a firewall rule that allows TCP traffic through port 80 and
determines a list of specific IP address blocks that are allowed to make
inbound connections, run:

  $ {command} tcp-rule --allow=tcp:80
      --source-ranges="10.0.0.0/22,10.0.0.0/14" --description="Narrowing TCP traffic"

To list existing firewall rules, run:

  $ gcloud compute firewall-rules list

For more detailed examples see
[](https://cloud.google.com/vpc/docs/using-firewalls)
  """,
}
