1# -*- coding: utf-8 -*-
2# Copyright 2017 Google Inc. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Implementation of requesterpays configuration command for buckets."""
16
17from __future__ import absolute_import
18from __future__ import print_function
19from __future__ import division
20from __future__ import unicode_literals
21
22from gslib import metrics
23from gslib.command import Command
24from gslib.command_argument import CommandArgument
25from gslib.cs_api_map import ApiSelector
26from gslib.exception import CommandException
27from gslib.exception import NO_URLS_MATCHED_TARGET
28from gslib.help_provider import CreateHelpText
29from gslib.third_party.storage_apitools import storage_v1_messages as apitools_messages
30from gslib.utils.constants import NO_MAX
31
32_SET_SYNOPSIS = """
33  gsutil requesterpays set <on|off> bucket_url...
34"""
35
36_GET_SYNOPSIS = """
37  gsutil requesterpays get bucket_url...
38"""
39
40_SYNOPSIS = _SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n')
41
42_SET_DESCRIPTION = """
43<B>SET</B>
44  The "set" sub-command requires an additional sub-command, either "on" or
45  "off", which, respectively, will enable or disable requester pays for the
46  specified bucket(s).
47
48"""
49
50_GET_DESCRIPTION = """
51<B>GET</B>
52  The "get" sub-command gets the requester pays configuration for a
53  bucket and displays whether or not it is enabled.
54"""
55
56_DESCRIPTION = """
57  The Requester Pays Configuration feature enables you to configure a Google
58  Cloud Storage bucket to indicate that the requester will pay all costs
59  related to accessing the bucket and its objects.
60
61  The gsutil requesterpays command has two sub-commands:
62""" + _SET_DESCRIPTION + _GET_DESCRIPTION
63
64_DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION)
65
66_get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION)
67_set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION)
68
69
70class RequesterPaysCommand(Command):
71  """Implementation of gsutil requesterpays command."""
72
73  # Command specification. See base class for documentation.
74  command_spec = Command.CreateCommandSpec(
75      'requesterpays',
76      usage_synopsis=_SYNOPSIS,
77      min_args=2,
78      max_args=NO_MAX,
79      supported_sub_args='',
80      file_url_ok=False,
81      provider_url_ok=False,
82      urls_start_arg=2,
83      gs_api_support=[
84          # ApiSelector.XML,  # TODO: Uncomment once boto changes are added.
85          ApiSelector.JSON,
86      ],
87      gs_default_api=ApiSelector.JSON,
88      argparse_arguments={
89          'set': [
90              CommandArgument('mode', choices=['on', 'off']),
91              CommandArgument.MakeZeroOrMoreCloudBucketURLsArgument(),
92          ],
93          'get': [CommandArgument.MakeZeroOrMoreCloudBucketURLsArgument(),]
94      },
95  )
96  # Help specification. See help_provider.py for documentation.
97  help_spec = Command.HelpSpec(
98      help_name='requesterpays',
99      help_name_aliases=[],
100      help_type='command_help',
101      help_one_line_summary=(
102          'Enable or disable requester pays for one or more buckets'),
103      help_text=_DETAILED_HELP_TEXT,
104      subcommand_help_text={
105          'get': _get_help_text,
106          'set': _set_help_text,
107      },
108  )
109
110  def _CalculateUrlsStartArg(self):
111    if not self.args:
112      self.RaiseWrongNumberOfArgumentsException()
113    if self.args[0].lower() == 'set':
114      return 2
115    else:
116      return 1
117
118  def _SetRequesterPays(self):
119    """Gets requesterpays configuration for a bucket."""
120    requesterpays_arg = self.args[0].lower()
121    if requesterpays_arg not in ('on', 'off'):
122      raise CommandException('Argument to "%s set" must be either <on|off>' %
123                             (self.command_name))
124    url_args = self.args[1:]
125    if not url_args:
126      self.RaiseWrongNumberOfArgumentsException()
127
128    # Iterate over URLs, expanding wildcards and set the requesterpays
129    # configuration on each.
130    some_matched = False
131    for url_str in url_args:
132      bucket_iter = self.GetBucketUrlIterFromArg(url_str, bucket_fields=['id'])
133      for blr in bucket_iter:
134        url = blr.storage_url
135        some_matched = True
136        bucket_metadata = apitools_messages.Bucket(
137            billing=apitools_messages.Bucket.BillingValue())
138        if requesterpays_arg == 'on':
139          self.logger.info('Enabling requester pays for %s...', url)
140          bucket_metadata.billing.requesterPays = True
141        else:
142          self.logger.info('Disabling requester pays for %s...', url)
143          bucket_metadata.billing.requesterPays = False
144        self.gsutil_api.PatchBucket(url.bucket_name,
145                                    bucket_metadata,
146                                    provider=url.scheme,
147                                    fields=['id'])
148    if not some_matched:
149      raise CommandException(NO_URLS_MATCHED_TARGET % list(url_args))
150
151  def _GetRequesterPays(self):
152    """Gets requesterpays configuration for one or more buckets."""
153    url_args = self.args
154
155    # Iterate over URLs, expanding wildcards and getting the requesterpays
156    # configuration on each.
157    some_matched = False
158    for url_str in url_args:
159      bucket_iter = self.GetBucketUrlIterFromArg(url_str,
160                                                 bucket_fields=['billing'])
161      for blr in bucket_iter:
162        some_matched = True
163        if blr.root_object.billing and blr.root_object.billing.requesterPays:
164          print('%s: Enabled' % blr.url_string.rstrip('/'))
165        else:
166          print('%s: Disabled' % blr.url_string.rstrip('/'))
167    if not some_matched:
168      raise CommandException(NO_URLS_MATCHED_TARGET % list(url_args))
169
170  def RunCommand(self):
171    """Command entry point for the requesterpays command."""
172    action_subcommand = self.args.pop(0)
173    if action_subcommand == 'get':
174      func = self._GetRequesterPays
175      metrics.LogCommandParams(subcommands=[action_subcommand])
176    elif action_subcommand == 'set':
177      func = self._SetRequesterPays
178      requesterpays_arg = self.args[0].lower()
179      if requesterpays_arg in ('on', 'off'):
180        metrics.LogCommandParams(
181            subcommands=[action_subcommand, requesterpays_arg])
182    else:
183      raise CommandException(
184          ('Invalid subcommand "%s" for the %s command.\n'
185           'See "gsutil help %s".') %
186          (action_subcommand, self.command_name, self.command_name))
187    func()
188    return 0
189