1# -*- coding: utf-8 -*- 2# Copyright 2018 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"""This module provides the command to gsutil.""" 16 17from __future__ import absolute_import 18from __future__ import print_function 19 20import getopt 21import textwrap 22 23from gslib import metrics 24from gslib.command import Command 25from gslib.command_argument import CommandArgument 26from gslib.cs_api_map import ApiSelector 27from gslib.exception import CommandException 28from gslib.exception import NO_URLS_MATCHED_TARGET 29from gslib.help_provider import CreateHelpText 30from gslib.third_party.storage_apitools import storage_v1_messages as apitools_messages 31from gslib.utils.constants import NO_MAX 32from gslib.utils.text_util import InsistOnOrOff 33 34_SET_SYNOPSIS = """ 35 gsutil ubla set (on|off) gs://<bucket_name>... 36""" 37 38_GET_SYNOPSIS = """ 39 gsutil ubla get bucket_url... 40""" 41 42_SYNOPSIS = _SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') 43 44_SET_DESCRIPTION = """ 45<B>SET</B> 46 The ``ubla set`` command enables or disables uniform 47 bucket-level access for Google Cloud Storage buckets. 48 49<B>SET EXAMPLES</B> 50 Configure your buckets to use uniform bucket-level access: 51 52 gsutil ubla set on gs://redbucket gs://bluebucket 53 54 Configure your buckets to NOT use uniform bucket-level access: 55 56 gsutil ubla set off gs://redbucket gs://bluebucket 57""" 58 59_GET_DESCRIPTION = """ 60<B>GET</B> 61 The ``ubla get`` command shows whether uniform bucket-level access is enabled 62 for the specified Cloud Storage bucket(s). 63 64<B>GET EXAMPLES</B> 65 Check if your buckets are using uniform bucket-level access: 66 67 gsutil ubla get gs://redbucket gs://bluebucket 68""" 69 70_DESCRIPTION = """ 71 The ``ubla`` command is used to retrieve or configure the 72 `uniform bucket-level access 73 <https://cloud.google.com/storage/docs/bucket-policy-only>`_ setting of 74 Cloud Storage bucket(s). This command has two sub-commands, ``get`` and 75 ``set``. 76""" + _GET_DESCRIPTION + _SET_DESCRIPTION 77 78_DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION) 79_set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION) 80_get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION) 81 82# Aliases to make these more likely to fit on one line. 83IamConfigurationValue = apitools_messages.Bucket.IamConfigurationValue 84uniformBucketLevelAccessValue = IamConfigurationValue.BucketPolicyOnlyValue 85 86 87class UblaCommand(Command): 88 """Implements the gsutil ubla command.""" 89 90 command_spec = Command.CreateCommandSpec( 91 'ubla', 92 command_name_aliases=['uniformbucketlevelaccess'], 93 usage_synopsis=_SYNOPSIS, 94 min_args=2, 95 max_args=NO_MAX, 96 supported_sub_args='', 97 file_url_ok=False, 98 provider_url_ok=False, 99 urls_start_arg=2, 100 gs_api_support=[ApiSelector.JSON], 101 gs_default_api=ApiSelector.JSON, 102 argparse_arguments={ 103 'get': [CommandArgument.MakeNCloudURLsArgument(1),], 104 'set': [ 105 CommandArgument('mode', choices=['on', 'off']), 106 CommandArgument.MakeZeroOrMoreCloudBucketURLsArgument() 107 ], 108 }) 109 # Help specification. See help_provider.py for documentation. 110 help_spec = Command.HelpSpec( 111 help_name='ubla', 112 help_name_aliases=['uniformbucketlevelaccess'], 113 help_type='command_help', 114 help_one_line_summary='Configure Uniform bucket-level access', 115 help_text=_DETAILED_HELP_TEXT, 116 subcommand_help_text={ 117 'get': _get_help_text, 118 'set': _set_help_text, 119 }, 120 ) 121 122 def _ValidateBucketListingRefAndReturnBucketName(self, blr): 123 if blr.storage_url.scheme != 'gs': 124 raise CommandException( 125 'The %s command can only be used with gs:// bucket URLs.' % 126 self.command_name) 127 128 def _GetUbla(self, blr): 129 """Gets the Uniform bucket-level access setting for a bucket.""" 130 self._ValidateBucketListingRefAndReturnBucketName(blr) 131 bucket_url = blr.storage_url 132 133 bucket_metadata = self.gsutil_api.GetBucket(bucket_url.bucket_name, 134 fields=['iamConfiguration'], 135 provider=bucket_url.scheme) 136 iam_config = bucket_metadata.iamConfiguration 137 # TODO(mynameisrafe): Replace bucketPolicyOnly with uniformBucketLevelAccess 138 # when the property is live. 139 uniform_bucket_level_access = iam_config.bucketPolicyOnly 140 141 fields = { 142 'bucket': str(bucket_url).rstrip('/'), 143 'enabled': uniform_bucket_level_access.enabled 144 } 145 146 locked_time_line = '' 147 if uniform_bucket_level_access.lockedTime: 148 fields['locked_time'] = uniform_bucket_level_access.lockedTime 149 locked_time_line = ' LockedTime: {locked_time}\n' 150 151 if uniform_bucket_level_access: 152 print(('Uniform bucket-level access setting for {bucket}:\n' 153 ' Enabled: {enabled}\n' + locked_time_line).format(**fields)) 154 155 def _SetUbla(self, blr, setting_arg): 156 """Sets the Uniform bucket-level access setting for a bucket on or off.""" 157 self._ValidateBucketListingRefAndReturnBucketName(blr) 158 bucket_url = blr.storage_url 159 160 iam_config = IamConfigurationValue() 161 # TODO(mynameisrafe): Replace bucketPolicyOnly with uniformBucketLevelAccess 162 # when the property is live. 163 iam_config.bucketPolicyOnly = uniformBucketLevelAccessValue() 164 iam_config.bucketPolicyOnly.enabled = (setting_arg == 'on') 165 166 bucket_metadata = apitools_messages.Bucket(iamConfiguration=iam_config) 167 168 setting_verb = 'Enabling' if setting_arg == 'on' else 'Disabling' 169 print('%s Uniform bucket-level access for %s...' % 170 (setting_verb, str(bucket_url).rstrip('/'))) 171 172 self.gsutil_api.PatchBucket(bucket_url.bucket_name, 173 bucket_metadata, 174 fields=['iamConfiguration'], 175 provider=bucket_url.scheme) 176 return 0 177 178 def _Ubla(self): 179 """Handles ubla command on a Cloud Storage bucket.""" 180 subcommand = self.args.pop(0) 181 182 if subcommand not in ('get', 'set'): 183 raise CommandException('ubla only supports get|set') 184 185 subcommand_func = None 186 subcommand_args = [] 187 setting_arg = None 188 189 if subcommand == 'get': 190 subcommand_func = self._GetUbla 191 elif subcommand == 'set': 192 subcommand_func = self._SetUbla 193 setting_arg = self.args.pop(0) 194 InsistOnOrOff(setting_arg, 195 'Only on and off values allowed for set option') 196 subcommand_args.append(setting_arg) 197 198 # Iterate over bucket args, performing the specified subsubcommand. 199 some_matched = False 200 url_args = self.args 201 if not url_args: 202 self.RaiseWrongNumberOfArgumentsException() 203 for url_str in url_args: 204 # Throws a CommandException if the argument is not a bucket. 205 bucket_iter = self.GetBucketUrlIterFromArg(url_str) 206 for bucket_listing_ref in bucket_iter: 207 some_matched = True 208 subcommand_func(bucket_listing_ref, *subcommand_args) 209 210 if not some_matched: 211 raise CommandException(NO_URLS_MATCHED_TARGET % list(url_args)) 212 return 0 213 214 def RunCommand(self): 215 """Command entry point for the ubla command.""" 216 if self.gsutil_api.GetApiSelector(provider='gs') != ApiSelector.JSON: 217 raise CommandException('\n'.join( 218 textwrap.wrap( 219 'The "%s" command can only be used with the Cloud Storage JSON API.' 220 % self.command_name))) 221 222 action_subcommand = self.args[0] 223 self.ParseSubOpts(check_args=True) 224 225 if action_subcommand == 'get' or action_subcommand == 'set': 226 metrics.LogCommandParams(sub_opts=self.sub_opts) 227 metrics.LogCommandParams(subcommands=[action_subcommand]) 228 self._Ubla() 229 else: 230 raise CommandException('Invalid subcommand "%s", use get|set instead.' % 231 action_subcommand) 232