| OLD | NEW |
| 1 # -*- coding: utf-8 -*- |
| 1 # Copyright 2011 Google Inc. All Rights Reserved. | 2 # Copyright 2011 Google Inc. All Rights Reserved. |
| 2 # | 3 # |
| 3 # Licensed under the Apache License, Version 2.0 (the "License"); | 4 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 # you may not use this file except in compliance with the License. | 5 # you may not use this file except in compliance with the License. |
| 5 # You may obtain a copy of the License at | 6 # You may obtain a copy of the License at |
| 6 # | 7 # |
| 7 # http://www.apache.org/licenses/LICENSE-2.0 | 8 # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 # | 9 # |
| 9 # Unless required by applicable law or agreed to in writing, software | 10 # Unless required by applicable law or agreed to in writing, software |
| 10 # distributed under the License is distributed on an "AS IS" BASIS, | 11 # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 # See the License for the specific language governing permissions and | 13 # See the License for the specific language governing permissions and |
| 13 # limitations under the License. | 14 # limitations under the License. |
| 15 """Implementation of default object acl command for Google Cloud Storage.""" |
| 16 |
| 17 from __future__ import absolute_import |
| 14 | 18 |
| 15 import getopt | 19 import getopt |
| 16 | 20 |
| 17 from boto.exception import GSResponseError | |
| 18 from gslib import aclhelpers | 21 from gslib import aclhelpers |
| 22 from gslib.cloud_api import AccessDeniedException |
| 23 from gslib.cloud_api import BadRequestException |
| 24 from gslib.cloud_api import Preconditions |
| 25 from gslib.cloud_api import ServiceException |
| 19 from gslib.command import Command | 26 from gslib.command import Command |
| 20 from gslib.command import COMMAND_NAME | 27 from gslib.command import SetAclExceptionHandler |
| 21 from gslib.command import COMMAND_NAME_ALIASES | 28 from gslib.command import SetAclFuncWrapper |
| 22 from gslib.command import FILE_URIS_OK | 29 from gslib.cs_api_map import ApiSelector |
| 23 from gslib.command import MAX_ARGS | |
| 24 from gslib.command import MIN_ARGS | |
| 25 from gslib.command import PROVIDER_URIS_OK | |
| 26 from gslib.command import SUPPORTED_SUB_ARGS | |
| 27 from gslib.command import URIS_START_ARG | |
| 28 from gslib.exception import CommandException | 30 from gslib.exception import CommandException |
| 29 from gslib.help_provider import CreateHelpText | 31 from gslib.help_provider import CreateHelpText |
| 30 from gslib.help_provider import HELP_NAME | 32 from gslib.storage_url import StorageUrlFromString |
| 31 from gslib.help_provider import HELP_NAME_ALIASES | 33 from gslib.third_party.storage_apitools import storage_v1_messages as apitools_m
essages |
| 32 from gslib.help_provider import HELP_ONE_LINE_SUMMARY | |
| 33 from gslib.help_provider import HELP_TEXT | |
| 34 from gslib.help_provider import HelpType | |
| 35 from gslib.help_provider import HELP_TYPE | |
| 36 from gslib.help_provider import SUBCOMMAND_HELP_TEXT | |
| 37 from gslib.util import NO_MAX | 34 from gslib.util import NO_MAX |
| 38 from gslib.util import Retry | 35 from gslib.util import Retry |
| 36 from gslib.util import UrlsAreForSingleProvider |
| 39 | 37 |
| 40 _SET_SYNOPSIS = """ | 38 _SET_SYNOPSIS = """ |
| 41 gsutil defacl set file-or-canned_acl_name uri... | 39 gsutil defacl set file-or-canned_acl_name url... |
| 42 """ | 40 """ |
| 43 | 41 |
| 44 _GET_SYNOPSIS = """ | 42 _GET_SYNOPSIS = """ |
| 45 gsutil defacl get uri | 43 gsutil defacl get url |
| 46 """ | 44 """ |
| 47 | 45 |
| 48 _CH_SYNOPSIS = """ | 46 _CH_SYNOPSIS = """ |
| 49 gsutil defacl ch -u|-g|-d <grant>... uri... | 47 gsutil defacl ch -u|-g|-d <grant>... url... |
| 50 """ | 48 """ |
| 51 | 49 |
| 52 _SET_DESCRIPTION = """ | 50 _SET_DESCRIPTION = """ |
| 53 <B>SET</B> | 51 <B>SET</B> |
| 54 The "defacl set" command sets default object ACLs for the specified buckets. | 52 The "defacl set" command sets default object ACLs for the specified buckets. |
| 55 If you specify a default object ACL for a certain bucket, Google Cloud | 53 If you specify a default object ACL for a certain bucket, Google Cloud |
| 56 Storage applies the default object ACL to all new objects uploaded to that | 54 Storage applies the default object ACL to all new objects uploaded to that |
| 57 bucket. | 55 bucket. |
| 58 | 56 |
| 59 Similar to the "acl set" command, the file-or-canned_acl_name names either a | 57 Similar to the "acl set" command, the file-or-canned_acl_name names either a |
| 60 canned ACL or the path to a file that contains ACL XML. (See "gsutil | 58 canned ACL or the path to a file that contains ACL text. (See "gsutil |
| 61 help acl" for examples of editing and setting ACLs via the | 59 help acl" for examples of editing and setting ACLs via the |
| 62 acl command.) | 60 acl command.) |
| 63 | 61 |
| 64 If you don't set a default object ACL on a bucket, the bucket's default | 62 If you don't set a default object ACL on a bucket, the bucket's default |
| 65 object ACL will be project-private. | 63 object ACL will be project-private. |
| 66 | 64 |
| 67 Setting a default object ACL on a bucket provides a convenient way | 65 Setting a default object ACL on a bucket provides a convenient way |
| 68 to ensure newly uploaded objects have a specific ACL, and avoids the | 66 to ensure newly uploaded objects have a specific ACL, and avoids the |
| 69 need to back after the fact and set ACLs on a large number of objects | 67 need to back after the fact and set ACLs on a large number of objects |
| 70 for which you forgot to set the ACL at object upload time (which can | 68 for which you forgot to set the ACL at object upload time (which can |
| 71 happen if you don't set a default object ACL on a bucket, and get the | 69 happen if you don't set a default object ACL on a bucket, and get the |
| 72 default project-private ACL). | 70 default project-private ACL). |
| 73 """ | 71 """ |
| 74 | 72 |
| 75 _GET_DESCRIPTION = """ | 73 _GET_DESCRIPTION = """ |
| 76 <B>GET</B> | 74 <B>GET</B> |
| 77 Gets the default ACL XML for a bucket, which you can save and edit | 75 Gets the default ACL text for a bucket, which you can save and edit |
| 78 for use with the "defacl set" command. | 76 for use with the "defacl set" command. |
| 79 """ | 77 """ |
| 80 | 78 |
| 81 _CH_DESCRIPTION = """ | 79 _CH_DESCRIPTION = """ |
| 82 <B>CH</B> | 80 <B>CH</B> |
| 83 The "defacl ch" (or "defacl change") command updates the default object | 81 The "defacl ch" (or "defacl change") command updates the default object |
| 84 access control list for a bucket. The syntax is shared with the "acl ch" | 82 access control list for a bucket. The syntax is shared with the "acl ch" |
| 85 command, so see the "CH" section of "gsutil help acl" for the full help | 83 command, so see the "CH" section of "gsutil help acl" for the full help |
| 86 description. | 84 description. |
| 87 | 85 |
| 88 <B>CH EXAMPLES</B> | 86 <B>CH EXAMPLES</B> |
| 89 Add the user john.doe@example.com to the default object ACL on bucket | 87 Add the user john.doe@example.com to the default object ACL on bucket |
| 90 example-bucket with READ access: | 88 example-bucket with READ access: |
| 91 | 89 |
| 92 gsutil defacl ch -u john.doe@example.com:READ gs://example-bucket | 90 gsutil defacl ch -u john.doe@example.com:READ gs://example-bucket |
| 93 | 91 |
| 94 Add the group admins@example.com to the default object ACL on bucket | 92 Add the group admins@example.com to the default object ACL on bucket |
| 95 example-bucket with FULL_CONTROL access: | 93 example-bucket with OWNER access: |
| 96 | 94 |
| 97 gsutil defacl ch -g admins@example.com:FC gs://example-bucket | 95 gsutil defacl ch -g admins@example.com:O gs://example-bucket |
| 98 """ | 96 """ |
| 99 | 97 |
| 100 _SYNOPSIS = (_SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') + | 98 _SYNOPSIS = (_SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') + |
| 101 _CH_SYNOPSIS.lstrip('\n') + '\n\n') | 99 _CH_SYNOPSIS.lstrip('\n') + '\n\n') |
| 102 | 100 |
| 103 _DESCRIPTION = """ | 101 _DESCRIPTION = """ |
| 104 The defacl command has three sub-commands: | 102 The defacl command has three sub-commands: |
| 105 """ + '\n'.join([_SET_DESCRIPTION + _GET_DESCRIPTION + _CH_DESCRIPTION]) | 103 """ + '\n'.join([_SET_DESCRIPTION + _GET_DESCRIPTION + _CH_DESCRIPTION]) |
| 106 | 104 |
| 107 _detailed_help_text = CreateHelpText(_SYNOPSIS, _DESCRIPTION) | 105 _DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION) |
| 108 | 106 |
| 109 _get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION) | 107 _get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION) |
| 110 _set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION) | 108 _set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION) |
| 111 _ch_help_text = CreateHelpText(_CH_SYNOPSIS, _CH_DESCRIPTION) | 109 _ch_help_text = CreateHelpText(_CH_SYNOPSIS, _CH_DESCRIPTION) |
| 112 | 110 |
| 113 | 111 |
| 114 class DefAclCommand(Command): | 112 class DefAclCommand(Command): |
| 115 """Implementation of gsutil defacl command.""" | 113 """Implementation of gsutil defacl command.""" |
| 116 | 114 |
| 117 # Command specification (processed by parent class). | 115 # Command specification. See base class for documentation. |
| 118 command_spec = { | 116 command_spec = Command.CreateCommandSpec( |
| 119 # Name of command. | 117 'defacl', |
| 120 COMMAND_NAME : 'defacl', | 118 command_name_aliases=['setdefacl', 'getdefacl', 'chdefacl'], |
| 121 # List of command name aliases. | 119 min_args=2, |
| 122 COMMAND_NAME_ALIASES : ['setdefacl', 'getdefacl', 'chdefacl'], | 120 max_args=NO_MAX, |
| 123 # Min number of args required by this command. | 121 supported_sub_args='fg:u:d:', |
| 124 MIN_ARGS : 2, | 122 file_url_ok=False, |
| 125 # Max number of args required by this command, or NO_MAX. | 123 provider_url_ok=False, |
| 126 MAX_ARGS : NO_MAX, | 124 urls_start_arg=1, |
| 127 # Getopt-style string specifying acceptable sub args. | 125 gs_api_support=[ApiSelector.XML, ApiSelector.JSON], |
| 128 SUPPORTED_SUB_ARGS : 'fg:u:d:', | 126 gs_default_api=ApiSelector.JSON, |
| 129 # True if file URIs acceptable for this command. | 127 ) |
| 130 FILE_URIS_OK : False, | 128 # Help specification. See help_provider.py for documentation. |
| 131 # True if provider-only URIs acceptable for this command. | 129 help_spec = Command.HelpSpec( |
| 132 PROVIDER_URIS_OK : False, | 130 help_name='defacl', |
| 133 # Index in args of first URI arg. | 131 help_name_aliases=[ |
| 134 URIS_START_ARG : 1, | 132 'default acl', 'setdefacl', 'getdefacl', 'chdefacl'], |
| 135 } | 133 help_type='command_help', |
| 136 help_spec = { | 134 help_one_line_summary='Get, set, or change default ACL on buckets', |
| 137 # Name of command or auxiliary help info for which this help applies. | 135 help_text=_DETAILED_HELP_TEXT, |
| 138 HELP_NAME : 'defacl', | 136 subcommand_help_text={ |
| 139 # List of help name aliases. | 137 'get': _get_help_text, 'set': _set_help_text, 'ch': _ch_help_text}, |
| 140 HELP_NAME_ALIASES : ['default acl', 'setdefacl', 'getdefacl', 'chdefacl'], | 138 ) |
| 141 # Type of help: | |
| 142 HELP_TYPE : HelpType.COMMAND_HELP, | |
| 143 # One line summary of this help. | |
| 144 HELP_ONE_LINE_SUMMARY : 'Get, set, or change default ACL on buckets', | |
| 145 # The full help text. | |
| 146 HELP_TEXT : _detailed_help_text, | |
| 147 # Help text for sub-commands. | |
| 148 SUBCOMMAND_HELP_TEXT : {'get' : _get_help_text, | |
| 149 'set' : _set_help_text, | |
| 150 'ch' : _ch_help_text}, | |
| 151 } | |
| 152 | 139 |
| 153 def _CalculateUrisStartArg(self): | 140 def _CalculateUrlsStartArg(self): |
| 154 if not self.args: | 141 if not self.args: |
| 155 self._RaiseWrongNumberOfArgumentsException() | 142 self._RaiseWrongNumberOfArgumentsException() |
| 156 if (self.args[0].lower() == 'set'): | 143 if self.args[0].lower() == 'set': |
| 157 return 2 | 144 return 2 |
| 158 elif self.command_alias_used == 'getdefacl': | 145 elif self.command_alias_used == 'getdefacl': |
| 159 return 0 | 146 return 0 |
| 160 else: | 147 else: |
| 161 return 1 | 148 return 1 |
| 162 | 149 |
| 163 def _SetDefAcl(self): | 150 def _SetDefAcl(self): |
| 164 if not self.suri_builder.StorageUri(self.args[-1]).names_bucket(): | 151 if not StorageUrlFromString(self.args[-1]).IsBucket(): |
| 165 raise CommandException('URI must name a bucket for the %s command' % | 152 raise CommandException('URL must name a bucket for the %s command' % |
| 166 self.command_name) | 153 self.command_name) |
| 167 try: | 154 try: |
| 168 self.SetAclCommandHelper() | 155 self.SetAclCommandHelper(SetAclFuncWrapper, SetAclExceptionHandler) |
| 169 except GSResponseError as e: | 156 except AccessDeniedException: |
| 170 if e.code == 'AccessDenied' and e.reason == 'Forbidden' \ | 157 self._WarnServiceAccounts() |
| 171 and e.status == 403: | |
| 172 self._WarnServiceAccounts() | |
| 173 raise | 158 raise |
| 174 | 159 |
| 175 def _GetDefAcl(self): | 160 def _GetDefAcl(self): |
| 176 if not self.suri_builder.StorageUri(self.args[-1]).names_bucket(): | 161 if not StorageUrlFromString(self.args[0]).IsBucket(): |
| 177 raise CommandException('URI must name a bucket for the %s command' % | 162 raise CommandException('URL must name a bucket for the %s command' % |
| 178 self.command_name) | 163 self.command_name) |
| 179 try: | 164 self.GetAndPrintAcl(self.args[0]) |
| 180 self.GetAclCommandHelper() | |
| 181 except GSResponseError as e: | |
| 182 if e.code == 'AccessDenied' and e.reason == 'Forbidden' \ | |
| 183 and e.status == 403: | |
| 184 self._WarnServiceAccounts() | |
| 185 raise | |
| 186 | 165 |
| 187 def _ChDefAcl(self): | 166 def _ChDefAcl(self): |
| 167 """Parses options and changes default object ACLs on specified buckets.""" |
| 168 self.parse_versions = True |
| 188 self.changes = [] | 169 self.changes = [] |
| 189 | 170 |
| 190 if self.sub_opts: | 171 if self.sub_opts: |
| 191 for o, a in self.sub_opts: | 172 for o, a in self.sub_opts: |
| 192 if o == '-g': | 173 if o == '-g': |
| 193 self.changes.append( | 174 self.changes.append( |
| 194 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.GROUP)) | 175 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.GROUP)) |
| 195 if o == '-u': | 176 if o == '-u': |
| 196 self.changes.append( | 177 self.changes.append( |
| 197 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.USER)) | 178 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.USER)) |
| 198 if o == '-d': | 179 if o == '-d': |
| 199 self.changes.append(aclhelpers.AclDel(a)) | 180 self.changes.append(aclhelpers.AclDel(a)) |
| 200 | 181 |
| 201 if not self.changes: | 182 if not self.changes: |
| 202 raise CommandException( | 183 raise CommandException( |
| 203 'Please specify at least one access change ' | 184 'Please specify at least one access change ' |
| 204 'with the -g, -u, or -d flags') | 185 'with the -g, -u, or -d flags') |
| 205 | 186 |
| 206 storage_uri = self.UrisAreForSingleProvider(self.args) | 187 if (not UrlsAreForSingleProvider(self.args) or |
| 207 if not (storage_uri and storage_uri.get_provider().name == 'google'): | 188 StorageUrlFromString(self.args[0]).scheme != 'gs'): |
| 208 raise CommandException( | 189 raise CommandException( |
| 209 'The "{0}" command can only be used with gs:// URIs'.format( | 190 'The "{0}" command can only be used with gs:// URLs'.format( |
| 210 self.command_name)) | 191 self.command_name)) |
| 211 | 192 |
| 212 bucket_uris = set() | 193 bucket_urls = set() |
| 213 for uri_arg in self.args: | 194 for url_arg in self.args: |
| 214 for result in self.WildcardIterator(uri_arg): | 195 for result in self.WildcardIterator(url_arg): |
| 215 uri = result.uri | 196 if not result.storage_url.IsBucket(): |
| 216 if not uri.names_bucket(): | |
| 217 raise CommandException( | 197 raise CommandException( |
| 218 'The defacl ch command can only be applied to buckets.') | 198 'The defacl ch command can only be applied to buckets.') |
| 219 bucket_uris.add(uri) | 199 bucket_urls.add(result.storage_url) |
| 220 | 200 |
| 221 for uri in bucket_uris: | 201 for storage_url in bucket_urls: |
| 222 self.ApplyAclChanges(uri) | 202 self.ApplyAclChanges(storage_url) |
| 223 | 203 |
| 224 @Retry(GSResponseError, tries=3, timeout_secs=1) | 204 @Retry(ServiceException, tries=3, timeout_secs=1) |
| 225 def ApplyAclChanges(self, uri): | 205 def ApplyAclChanges(self, url): |
| 226 """Applies the changes in self.changes to the provided URI.""" | 206 """Applies the changes in self.changes to the provided URL.""" |
| 227 try: | 207 bucket = self.gsutil_api.GetBucket( |
| 228 current_acl = uri.get_def_acl() | 208 url.bucket_name, provider=url.scheme, |
| 229 except GSResponseError as e: | 209 fields=['defaultObjectAcl', 'metageneration']) |
| 230 if (e.code == 'AccessDenied' and e.reason == 'Forbidden' | 210 current_acl = bucket.defaultObjectAcl |
| 231 and e.status == 403): | 211 if not current_acl: |
| 232 self._WarnServiceAccounts() | 212 self._WarnServiceAccounts() |
| 233 self.logger.warning('Failed to set default acl for {0}: {1}' | 213 self.logger.warning('Failed to set acl for %s. Please ensure you have ' |
| 234 .format(uri, e.reason)) | 214 'OWNER-role access to this resource.', url) |
| 235 return | 215 return |
| 236 | 216 |
| 237 modification_count = 0 | 217 modification_count = 0 |
| 238 for change in self.changes: | 218 for change in self.changes: |
| 239 modification_count += change.Execute(uri, current_acl, self.logger) | 219 modification_count += change.Execute( |
| 220 url, current_acl, 'defacl', self.logger) |
| 240 if modification_count == 0: | 221 if modification_count == 0: |
| 241 self.logger.info('No changes to {0}'.format(uri)) | 222 self.logger.info('No changes to %s', url) |
| 242 return | 223 return |
| 243 | 224 |
| 244 # TODO: Add if-metageneration-match when boto provides access to bucket | 225 try: |
| 245 # metageneration. | 226 preconditions = Preconditions(meta_gen_match=bucket.metageneration) |
| 227 bucket_metadata = apitools_messages.Bucket(defaultObjectAcl=current_acl) |
| 228 self.gsutil_api.PatchBucket(url.bucket_name, bucket_metadata, |
| 229 preconditions=preconditions, |
| 230 provider=url.scheme, fields=['id']) |
| 231 except BadRequestException as e: |
| 232 # Don't retry on bad requests, e.g. invalid email address. |
| 233 raise CommandException('Received bad request from server: %s' % str(e)) |
| 246 | 234 |
| 247 # If this fails because of a precondition, it will raise a | 235 self.logger.info('Updated default ACL on %s', url) |
| 248 # GSResponseError for @Retry to handle. | |
| 249 try: | |
| 250 uri.set_def_acl(current_acl, validate=False) | |
| 251 except GSResponseError as e: | |
| 252 # Don't retry on bad requests, e.g. invalid email address. | |
| 253 if getattr(e, 'status', None) == 400: | |
| 254 raise CommandException('Received bad request from server: %s' % str(e)) | |
| 255 raise | |
| 256 self.logger.info('Updated default ACL on {0}'.format(uri)) | |
| 257 | 236 |
| 258 # Command entry point. | |
| 259 def RunCommand(self): | 237 def RunCommand(self): |
| 238 """Command entry point for the defacl command.""" |
| 260 action_subcommand = self.args.pop(0) | 239 action_subcommand = self.args.pop(0) |
| 261 (self.sub_opts, self.args) = getopt.getopt(self.args, | 240 self.sub_opts, self.args = getopt.getopt( |
| 262 self.command_spec[SUPPORTED_SUB_ARGS]) | 241 self.args, self.command_spec.supported_sub_args) |
| 263 self.CheckArguments() | 242 self.CheckArguments() |
| 243 self.def_acl = True |
| 244 self.continue_on_error = False |
| 264 if action_subcommand == 'get': | 245 if action_subcommand == 'get': |
| 265 func = self._GetDefAcl | 246 func = self._GetDefAcl |
| 266 elif action_subcommand == 'set': | 247 elif action_subcommand == 'set': |
| 267 func = self._SetDefAcl | 248 func = self._SetDefAcl |
| 268 elif action_subcommand in ('ch', 'change'): | 249 elif action_subcommand in ('ch', 'change'): |
| 269 func = self._ChDefAcl | 250 func = self._ChDefAcl |
| 270 else: | 251 else: |
| 271 raise CommandException(('Invalid subcommand "%s" for the %s command.\n' | 252 raise CommandException(('Invalid subcommand "%s" for the %s command.\n' |
| 272 'See "gsutil help defacl".') % | 253 'See "gsutil help defacl".') % |
| 273 (action_subcommand, self.command_name)) | 254 (action_subcommand, self.command_name)) |
| 274 func() | 255 func() |
| 275 return 0 | 256 return 0 |
| OLD | NEW |