| 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 acl command for cloud storage providers.""" |
| 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 |
| 19 from gslib import name_expansion | 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 |
| 20 from gslib.command import Command | 26 from gslib.command import Command |
| 21 from gslib.command import COMMAND_NAME | 27 from gslib.command import SetAclExceptionHandler |
| 22 from gslib.command import COMMAND_NAME_ALIASES | 28 from gslib.command import SetAclFuncWrapper |
| 23 from gslib.command import FILE_URIS_OK | 29 from gslib.cs_api_map import ApiSelector |
| 24 from gslib.command import MAX_ARGS | |
| 25 from gslib.command import MIN_ARGS | |
| 26 from gslib.command import PROVIDER_URIS_OK | |
| 27 from gslib.command import SUPPORTED_SUB_ARGS | |
| 28 from gslib.command import URIS_START_ARG | |
| 29 from gslib.exception import CommandException | 30 from gslib.exception import CommandException |
| 30 from gslib.help_provider import CreateHelpText | 31 from gslib.help_provider import CreateHelpText |
| 31 from gslib.help_provider import HELP_NAME | 32 from gslib.storage_url import StorageUrlFromString |
| 32 from gslib.help_provider import HELP_NAME_ALIASES | 33 from gslib.third_party.storage_apitools import storage_v1_messages as apitools_m
essages |
| 33 from gslib.help_provider import HELP_ONE_LINE_SUMMARY | |
| 34 from gslib.help_provider import HELP_TEXT | |
| 35 from gslib.help_provider import HELP_TYPE | |
| 36 from gslib.help_provider import HelpType | |
| 37 from gslib.help_provider import SUBCOMMAND_HELP_TEXT | |
| 38 from gslib.util import NO_MAX | 34 from gslib.util import NO_MAX |
| 39 from gslib.util import Retry | 35 from gslib.util import Retry |
| 36 from gslib.util import UrlsAreForSingleProvider |
| 40 | 37 |
| 41 _SET_SYNOPSIS = """ | 38 _SET_SYNOPSIS = """ |
| 42 gsutil acl set [-f] [-R] [-a] file-or-canned_acl_name uri... | 39 gsutil acl set [-f] [-R] [-a] file-or-canned_acl_name url... |
| 43 """ | 40 """ |
| 44 | 41 |
| 45 _GET_SYNOPSIS = """ | 42 _GET_SYNOPSIS = """ |
| 46 gsutil acl get uri | 43 gsutil acl get url |
| 47 """ | 44 """ |
| 48 | 45 |
| 49 _CH_SYNOPSIS = """ | 46 _CH_SYNOPSIS = """ |
| 50 gsutil acl ch [-R] -u|-g|-d <grant>... uri... | 47 gsutil acl ch [-R] -u|-g|-d <grant>... url... |
| 51 | 48 |
| 52 where each <grant> is one of the following forms: | 49 where each <grant> is one of the following forms: |
| 53 | 50 |
| 54 -u <id|email>:<perm> | 51 -u <id|email>:<perm> |
| 55 -g <id|email|domain|All|AllAuth>:<perm> | 52 -g <id|email|domain|All|AllAuth>:<perm> |
| 56 -d <id|email|domain|All|AllAuth> | 53 -d <id|email|domain|All|AllAuth> |
| 57 """ | 54 """ |
| 58 | 55 |
| 59 _GET_DESCRIPTION = """ | 56 _GET_DESCRIPTION = """ |
| 60 <B>GET</B> | 57 <B>GET</B> |
| 61 The "acl get" command gets the ACL XML for a bucket or object, which you can | 58 The "acl get" command gets the ACL text for a bucket or object, which you can |
| 62 save and edit for the acl set command. | 59 save and edit for the acl set command. |
| 63 """ | 60 """ |
| 64 | 61 |
| 65 _SET_DESCRIPTION = """ | 62 _SET_DESCRIPTION = """ |
| 66 <B>SET</B> | 63 <B>SET</B> |
| 67 The "acl set" command allows you to set an Access Control List on one or | 64 The "acl set" command allows you to set an Access Control List on one or |
| 68 more buckets and objects. The simplest way to use it is to specify one of | 65 more buckets and objects. The simplest way to use it is to specify one of |
| 69 the canned ACLs, e.g.,: | 66 the canned ACLs, e.g.,: |
| 70 | 67 |
| 71 gsutil acl set private gs://bucket | 68 gsutil acl set private gs://bucket |
| (...skipping 25 matching lines...) Expand all Loading... |
| 97 for example: | 94 for example: |
| 98 | 95 |
| 99 gsutil acl set acl.txt gs://bucket/*.jpg | 96 gsutil acl set acl.txt gs://bucket/*.jpg |
| 100 | 97 |
| 101 If you have a large number of ACLs to update you might want to use the | 98 If you have a large number of ACLs to update you might want to use the |
| 102 gsutil -m option, to perform a parallel (multi-threaded/multi-processing) | 99 gsutil -m option, to perform a parallel (multi-threaded/multi-processing) |
| 103 update: | 100 update: |
| 104 | 101 |
| 105 gsutil -m acl set acl.txt gs://bucket/*.jpg | 102 gsutil -m acl set acl.txt gs://bucket/*.jpg |
| 106 | 103 |
| 107 Note that multi-threading/multi-processing is only done when the named URIs | 104 Note that multi-threading/multi-processing is only done when the named URLs |
| 108 refer to objects. gsutil -m acl set gs://bucket1 gs://bucket2 will run the | 105 refer to objects. gsutil -m acl set gs://bucket1 gs://bucket2 will run the |
| 109 acl set operations sequentially. | 106 acl set operations sequentially. |
| 110 | 107 |
| 111 | 108 |
| 112 <B>SET OPTIONS</B> | 109 <B>SET OPTIONS</B> |
| 113 The "set" sub-command has the following options | 110 The "set" sub-command has the following options |
| 114 | 111 |
| 115 -R, -r Performs "acl set" request recursively, to all objects under | 112 -R, -r Performs "acl set" request recursively, to all objects under |
| 116 the specified URI. | 113 the specified URL. |
| 117 | 114 |
| 118 -a Performs "acl set" request on all object versions. | 115 -a Performs "acl set" request on all object versions. |
| 119 | 116 |
| 120 -f Normally gsutil stops at the first error. The -f option causes | 117 -f Normally gsutil stops at the first error. The -f option causes |
| 121 it to continue when it encounters errors. With this option the | 118 it to continue when it encounters errors. If some of the ACLs |
| 122 gsutil exit status will be 0 even if some ACLs couldn't be | 119 couldn't be set, gsutil's exit status will be non-zero even if |
| 123 set. | 120 this flag is set. This option is implicitly set when running |
| 121 "gsutil -m acl...". |
| 124 """ | 122 """ |
| 125 | 123 |
| 126 _CH_DESCRIPTION = """ | 124 _CH_DESCRIPTION = """ |
| 127 <B>CH</B> | 125 <B>CH</B> |
| 128 The "acl ch" (or "acl change") command updates access control lists, similar | 126 The "acl ch" (or "acl change") command updates access control lists, similar |
| 129 in spirit to the Linux chmod command. You can specify multiple access grant | 127 in spirit to the Linux chmod command. You can specify multiple access grant |
| 130 additions and deletions in a single command run; all changes will be made | 128 additions and deletions in a single command run; all changes will be made |
| 131 atomically to each object in turn. For example, if the command requests | 129 atomically to each object in turn. For example, if the command requests |
| 132 deleting one grant and adding a different grant, the ACLs being updated will | 130 deleting one grant and adding a different grant, the ACLs being updated will |
| 133 never be left in an intermediate state where one grant has been deleted but | 131 never be left in an intermediate state where one grant has been deleted but |
| 134 the second grant not yet added. Each change specifies a user or group grant | 132 the second grant not yet added. Each change specifies a user or group grant |
| 135 to add or delete, and for grant additions, one of R, W, FC (for the | 133 to add or delete, and for grant additions, one of R, W, O (for the |
| 136 permission to be granted). A more formal description is provided in a later | 134 permission to be granted). A more formal description is provided in a later |
| 137 section; below we provide examples. | 135 section; below we provide examples. |
| 138 | 136 |
| 139 <B>CH EXAMPLES</B> | 137 <B>CH EXAMPLES</B> |
| 140 Examples for "ch" sub-command: | 138 Examples for "ch" sub-command: |
| 141 | 139 |
| 142 Grant the user john.doe@example.com WRITE access to the bucket | 140 Grant the user john.doe@example.com WRITE access to the bucket |
| 143 example-bucket: | 141 example-bucket: |
| 144 | 142 |
| 145 gsutil acl ch -u john.doe@example.com:WRITE gs://example-bucket | 143 gsutil acl ch -u john.doe@example.com:WRITE gs://example-bucket |
| 146 | 144 |
| 147 Grant the group admins@example.com FULL_CONTROL access to all jpg files in | 145 Grant the group admins@example.com OWNER access to all jpg files in |
| 148 the top level of example-bucket: | 146 the top level of example-bucket: |
| 149 | 147 |
| 150 gsutil acl ch -g admins@example.com:FC gs://example-bucket/*.jpg | 148 gsutil acl ch -g admins@example.com:O gs://example-bucket/*.jpg |
| 151 | 149 |
| 152 Grant the user with the specified canonical ID READ access to all objects | 150 Grant the user with the specified canonical ID READ access to all objects |
| 153 in example-bucket that begin with folder/: | 151 in example-bucket that begin with folder/: |
| 154 | 152 |
| 155 gsutil acl ch -R \\ | 153 gsutil acl ch -R \\ |
| 156 -u 84fac329bceSAMPLE777d5d22b8SAMPLE785ac2SAMPLE2dfcf7c4adf34da46:R \\ | 154 -u 84fac329bceSAMPLE777d5d22b8SAMPLE785ac2SAMPLE2dfcf7c4adf34da46:R \\ |
| 157 gs://example-bucket/folder/ | 155 gs://example-bucket/folder/ |
| 158 | 156 |
| 157 Grant the service account foo@developer.gserviceaccount.com WRITE access to |
| 158 the bucket example-bucket: |
| 159 |
| 160 gsutil acl ch -u foo@developer.gserviceaccount.com:W gs://example-bucket |
| 161 |
| 159 Grant all users from my-domain.org READ access to the bucket | 162 Grant all users from my-domain.org READ access to the bucket |
| 160 gcs.my-domain.org: | 163 gcs.my-domain.org: |
| 161 | 164 |
| 162 gsutil acl ch -g my-domain.org:R gs://gcs.my-domain.org | 165 gsutil acl ch -g my-domain.org:R gs://gcs.my-domain.org |
| 163 | 166 |
| 164 Remove any current access by john.doe@example.com from the bucket | 167 Remove any current access by john.doe@example.com from the bucket |
| 165 example-bucket: | 168 example-bucket: |
| 166 | 169 |
| 167 gsutil acl ch -d john.doe@example.com gs://example-bucket | 170 gsutil acl ch -d john.doe@example.com gs://example-bucket |
| 168 | 171 |
| 169 If you have a large number of objects to update, enabling multi-threading | 172 If you have a large number of objects to update, enabling multi-threading |
| 170 with the gsutil -m flag can significantly improve performance. The | 173 with the gsutil -m flag can significantly improve performance. The |
| 171 following command adds FULL_CONTROL for admin@example.org using | 174 following command adds OWNER for admin@example.org using |
| 172 multi-threading: | 175 multi-threading: |
| 173 | 176 |
| 174 gsutil -m acl ch -R -u admin@example.org:FC gs://example-bucket | 177 gsutil -m acl ch -R -u admin@example.org:O gs://example-bucket |
| 175 | 178 |
| 176 Grant READ access to everyone from my-domain.org and to all authenticated | 179 Grant READ access to everyone from my-domain.org and to all authenticated |
| 177 users, and grant FULL_CONTROL to admin@mydomain.org, for the buckets | 180 users, and grant OWNER to admin@mydomain.org, for the buckets |
| 178 my-bucket and my-other-bucket, with multi-threading enabled: | 181 my-bucket and my-other-bucket, with multi-threading enabled: |
| 179 | 182 |
| 180 gsutil -m acl ch -R -g my-domain.org:R -g AllAuth:R \\ | 183 gsutil -m acl ch -R -g my-domain.org:R -g AllAuth:R \\ |
| 181 -u admin@mydomain.org:FC gs://my-bucket/ gs://my-other-bucket | 184 -u admin@mydomain.org:O gs://my-bucket/ gs://my-other-bucket |
| 182 | 185 |
| 183 <B>CH PERMISSIONS</B> | 186 <B>CH ROLES</B> |
| 184 You may specify the following permissions with either their shorthand or | 187 You may specify the following roles with either their shorthand or |
| 185 their full name: | 188 their full name: |
| 186 | 189 |
| 187 R: READ | 190 R: READ |
| 188 W: WRITE | 191 W: WRITE |
| 189 FC: FULL_CONTROL | 192 O: OWNER |
| 190 | 193 |
| 191 <B>CH SCOPES</B> | 194 <B>CH ENTITIES</B> |
| 192 There are four different scopes: Users, Groups, All Authenticated Users, | 195 There are four different entity types: Users, Groups, All Authenticated Users, |
| 193 and All Users. | 196 and All Users. |
| 194 | 197 |
| 195 Users are added with -u and a plain ID or email address, as in | 198 Users are added with -u and a plain ID or email address, as in |
| 196 "-u john-doe@gmail.com:r" | 199 "-u john-doe@gmail.com:r". Note: Service Accounts are considered to be users. |
| 197 | 200 |
| 198 Groups are like users, but specified with the -g flag, as in | 201 Groups are like users, but specified with the -g flag, as in |
| 199 "-g power-users@example.com:fc". Groups may also be specified as a full | 202 "-g power-users@example.com:fc". Groups may also be specified as a full |
| 200 domain, as in "-g my-company.com:r". | 203 domain, as in "-g my-company.com:r". |
| 201 | 204 |
| 202 AllAuthenticatedUsers and AllUsers are specified directly, as | 205 AllAuthenticatedUsers and AllUsers are specified directly, as |
| 203 in "-g AllUsers:R" or "-g AllAuthenticatedUsers:FC". These are case | 206 in "-g AllUsers:R" or "-g AllAuthenticatedUsers:O". These are case |
| 204 insensitive, and may be shortened to "all" and "allauth", respectively. | 207 insensitive, and may be shortened to "all" and "allauth", respectively. |
| 205 | 208 |
| 206 Removing permissions is specified with the -d flag and an ID, email | 209 Removing roles is specified with the -d flag and an ID, email |
| 207 address, domain, or one of AllUsers or AllAuthenticatedUsers. | 210 address, domain, or one of AllUsers or AllAuthenticatedUsers. |
| 208 | 211 |
| 209 Many scopes can be specified on the same command line, allowing bundled | 212 Many entities' roles can be specified on the same command line, allowing |
| 210 changes to be executed in a single run. This will reduce the number of | 213 bundled changes to be executed in a single run. This will reduce the number of |
| 211 requests made to the server. | 214 requests made to the server. |
| 212 | 215 |
| 213 <B>CH OPTIONS</B> | 216 <B>CH OPTIONS</B> |
| 214 The "ch" sub-command has the following options | 217 The "ch" sub-command has the following options |
| 215 | 218 |
| 216 -R, -r Performs acl ch request recursively, to all objects under the | 219 -R, -r Performs acl ch request recursively, to all objects under the |
| 217 specified URI. | 220 specified URL. |
| 218 | 221 |
| 219 -u Add or modify a user permission as specified in the SCOPES | 222 -u Add or modify a user entity's role. |
| 220 and PERMISSIONS sections. | |
| 221 | 223 |
| 222 -g Add or modify a group permission as specified in the SCOPES | 224 -g Add or modify a group entity's role. |
| 223 and PERMISSIONS sections. | |
| 224 | 225 |
| 225 -d Remove all permissions associated with the matching argument, | 226 -d Remove all roles associated with the matching entity. |
| 226 as specified in the SCOPES and PERMISSIONS sections | 227 |
| 228 -f Normally gsutil stops at the first error. The -f option causes |
| 229 it to continue when it encounters errors. With this option the |
| 230 gsutil exit status will be 0 even if some ACLs couldn't be |
| 231 changed. |
| 227 """ | 232 """ |
| 228 | 233 |
| 229 _SYNOPSIS = (_SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') + | 234 _SYNOPSIS = (_SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') + |
| 230 _CH_SYNOPSIS.lstrip('\n') + '\n\n') | 235 _CH_SYNOPSIS.lstrip('\n') + '\n\n') |
| 231 | 236 |
| 232 _DESCRIPTION = (""" | 237 _DESCRIPTION = (""" |
| 233 The acl command has three sub-commands: | 238 The acl command has three sub-commands: |
| 234 """ + '\n'.join([_GET_DESCRIPTION, _SET_DESCRIPTION, _CH_DESCRIPTION])) | 239 """ + '\n'.join([_GET_DESCRIPTION, _SET_DESCRIPTION, _CH_DESCRIPTION])) |
| 235 | 240 |
| 236 _detailed_help_text = CreateHelpText(_SYNOPSIS, _DESCRIPTION) | 241 _DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION) |
| 237 | 242 |
| 238 _get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION) | 243 _get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION) |
| 239 _set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION) | 244 _set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION) |
| 240 _ch_help_text = CreateHelpText(_CH_SYNOPSIS, _CH_DESCRIPTION) | 245 _ch_help_text = CreateHelpText(_CH_SYNOPSIS, _CH_DESCRIPTION) |
| 241 | 246 |
| 247 |
| 242 def _ApplyExceptionHandler(cls, exception): | 248 def _ApplyExceptionHandler(cls, exception): |
| 243 cls.logger.error('Encountered a problem: {0}'.format(exception)) | 249 cls.logger.error('Encountered a problem: %s', exception) |
| 244 cls.everything_set_okay = False | 250 cls.everything_set_okay = False |
| 245 | 251 |
| 246 def _ApplyAclChangesWrapper(cls, uri_or_expansion_result): | 252 |
| 247 cls.ApplyAclChanges(uri_or_expansion_result) | 253 def _ApplyAclChangesWrapper(cls, url_or_expansion_result, thread_state=None): |
| 254 cls.ApplyAclChanges(url_or_expansion_result, thread_state=thread_state) |
| 248 | 255 |
| 249 | 256 |
| 250 class AclCommand(Command): | 257 class AclCommand(Command): |
| 251 """Implementation of gsutil acl command.""" | 258 """Implementation of gsutil acl command.""" |
| 252 | 259 |
| 253 # Command specification (processed by parent class). | 260 # Command specification. See base class for documentation. |
| 254 command_spec = { | 261 command_spec = Command.CreateCommandSpec( |
| 255 # Name of command. | 262 'acl', |
| 256 COMMAND_NAME : 'acl', | 263 command_name_aliases=['getacl', 'setacl', 'chacl'], |
| 257 # List of command name aliases. | 264 min_args=2, |
| 258 COMMAND_NAME_ALIASES : ['getacl', 'setacl', 'chacl'], | 265 max_args=NO_MAX, |
| 259 # Min number of args required by this command. | 266 supported_sub_args='afRrg:u:d:', |
| 260 MIN_ARGS : 2, | 267 file_url_ok=False, |
| 261 # Max number of args required by this command, or NO_MAX. | 268 provider_url_ok=False, |
| 262 MAX_ARGS : NO_MAX, | 269 urls_start_arg=1, |
| 263 # Getopt-style string specifying acceptable sub args. | 270 gs_api_support=[ApiSelector.XML, ApiSelector.JSON], |
| 264 SUPPORTED_SUB_ARGS : 'afRrvg:u:d:', | 271 gs_default_api=ApiSelector.JSON, |
| 265 # True if file URIs acceptable for this command. | 272 ) |
| 266 FILE_URIS_OK : False, | 273 # Help specification. See help_provider.py for documentation. |
| 267 # True if provider-only URIs acceptable for this command. | 274 help_spec = Command.HelpSpec( |
| 268 PROVIDER_URIS_OK : False, | 275 help_name='acl', |
| 269 # Index in args of first URI arg. | 276 help_name_aliases=['getacl', 'setacl', 'chmod', 'chacl'], |
| 270 URIS_START_ARG : 1, | 277 help_type='command_help', |
| 271 } | 278 help_one_line_summary='Get, set, or change bucket and/or object ACLs', |
| 272 help_spec = { | 279 help_text=_DETAILED_HELP_TEXT, |
| 273 # Name of command or auxiliary help info for which this help applies. | 280 subcommand_help_text={ |
| 274 HELP_NAME : 'acl', | 281 'get': _get_help_text, 'set': _set_help_text, 'ch': _ch_help_text}, |
| 275 # List of help name aliases. | 282 ) |
| 276 HELP_NAME_ALIASES : ['getacl', 'setacl', 'chmod', 'chacl'], | |
| 277 # Type of help: | |
| 278 HELP_TYPE : HelpType.COMMAND_HELP, | |
| 279 # One line summary of this help. | |
| 280 HELP_ONE_LINE_SUMMARY : 'Get, set, or change bucket and/or object ACLs', | |
| 281 # The full help text. | |
| 282 HELP_TEXT : _detailed_help_text, | |
| 283 # Help text for sub-commands. | |
| 284 SUBCOMMAND_HELP_TEXT : {'get' : _get_help_text, | |
| 285 'set' : _set_help_text, | |
| 286 'ch' : _ch_help_text}, | |
| 287 } | |
| 288 | 283 |
| 289 def _CalculateUrisStartArg(self): | 284 def _CalculateUrlsStartArg(self): |
| 290 if not self.args: | 285 if not self.args: |
| 291 self._RaiseWrongNumberOfArgumentsException() | 286 self._RaiseWrongNumberOfArgumentsException() |
| 292 if (self.args[0].lower() == 'set') or (self.command_alias_used == 'setacl'): | 287 if (self.args[0].lower() == 'set') or (self.command_alias_used == 'setacl'): |
| 293 return 1 | 288 return 1 |
| 294 else: | 289 else: |
| 295 return 0 | 290 return 0 |
| 296 | 291 |
| 297 def _SetAcl(self): | 292 def _SetAcl(self): |
| 293 """Parses options and sets ACLs on the specified buckets/objects.""" |
| 298 self.continue_on_error = False | 294 self.continue_on_error = False |
| 299 if self.sub_opts: | 295 if self.sub_opts: |
| 300 for o, unused_a in self.sub_opts: | 296 for o, unused_a in self.sub_opts: |
| 301 if o == '-a': | 297 if o == '-a': |
| 302 self.all_versions = True | 298 self.all_versions = True |
| 303 elif o == '-f': | 299 elif o == '-f': |
| 304 self.continue_on_error = True | 300 self.continue_on_error = True |
| 305 elif o == '-r' or o == '-R': | 301 elif o == '-r' or o == '-R': |
| 306 self.recursion_requested = True | 302 self.recursion_requested = True |
| 307 elif o == '-v': | |
| 308 self.logger.warning('WARNING: The %s -v option is no longer' | |
| 309 ' needed, and will eventually be ' | |
| 310 'removed.\n' % self.command_name) | |
| 311 try: | 303 try: |
| 312 self.SetAclCommandHelper() | 304 self.SetAclCommandHelper(SetAclFuncWrapper, SetAclExceptionHandler) |
| 313 except GSResponseError as e: | 305 except AccessDeniedException, unused_e: |
| 314 if e.code == 'AccessDenied' and e.reason == 'Forbidden' \ | 306 self._WarnServiceAccounts() |
| 315 and e.status == 403: | |
| 316 self._WarnServiceAccounts() | |
| 317 raise | 307 raise |
| 308 if not self.everything_set_okay: |
| 309 raise CommandException('ACLs for some objects could not be set.') |
| 318 | 310 |
| 319 def _GetAcl(self): | |
| 320 try: | |
| 321 self.GetAclCommandHelper() | |
| 322 except GSResponseError as e: | |
| 323 if e.code == 'AccessDenied' and e.reason == 'Forbidden' \ | |
| 324 and e.status == 403: | |
| 325 self._WarnServiceAccounts() | |
| 326 raise | |
| 327 | |
| 328 def _ChAcl(self): | 311 def _ChAcl(self): |
| 312 """Parses options and changes ACLs on the specified buckets/objects.""" |
| 313 self.parse_versions = True |
| 329 self.changes = [] | 314 self.changes = [] |
| 315 self.continue_on_error = False |
| 330 | 316 |
| 331 if self.sub_opts: | 317 if self.sub_opts: |
| 332 for o, a in self.sub_opts: | 318 for o, a in self.sub_opts: |
| 319 if o == '-f': |
| 320 self.continue_on_error = True |
| 333 if o == '-g': | 321 if o == '-g': |
| 334 self.changes.append( | 322 self.changes.append( |
| 335 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.GROUP)) | 323 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.GROUP)) |
| 336 if o == '-u': | 324 if o == '-u': |
| 337 self.changes.append( | 325 self.changes.append( |
| 338 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.USER)) | 326 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.USER)) |
| 339 if o == '-d': | 327 if o == '-d': |
| 340 self.changes.append(aclhelpers.AclDel(a)) | 328 self.changes.append(aclhelpers.AclDel(a)) |
| 341 if o == '-r' or o == '-R': | 329 if o == '-r' or o == '-R': |
| 342 self.recursion_requested = True | 330 self.recursion_requested = True |
| 343 | 331 |
| 344 if not self.changes: | 332 if not self.changes: |
| 345 raise CommandException( | 333 raise CommandException( |
| 346 'Please specify at least one access change ' | 334 'Please specify at least one access change ' |
| 347 'with the -g, -u, or -d flags') | 335 'with the -g, -u, or -d flags') |
| 348 | 336 |
| 349 storage_uri = self.UrisAreForSingleProvider(self.args) | 337 if (not UrlsAreForSingleProvider(self.args) or |
| 350 if not (storage_uri and storage_uri.get_provider().name == 'google'): | 338 StorageUrlFromString(self.args[0]).scheme != 'gs'): |
| 351 raise CommandException( | 339 raise CommandException( |
| 352 'The "{0}" command can only be used with gs:// URIs'.format( | 340 'The "{0}" command can only be used with gs:// URLs'.format( |
| 353 self.command_name)) | 341 self.command_name)) |
| 354 | 342 |
| 355 bulk_uris = set() | |
| 356 for uri_arg in self.args: | |
| 357 for result in self.WildcardIterator(uri_arg): | |
| 358 uri = result.uri | |
| 359 if uri.names_bucket(): | |
| 360 if self.recursion_requested: | |
| 361 bulk_uris.add(uri.clone_replace_name('*').uri) | |
| 362 else: | |
| 363 # If applying to a bucket directly, the threading machinery will | |
| 364 # break, so we have to apply now, in the main thread. | |
| 365 self.ApplyAclChanges(uri) | |
| 366 else: | |
| 367 bulk_uris.add(uri_arg) | |
| 368 | |
| 369 try: | |
| 370 name_expansion_iterator = name_expansion.NameExpansionIterator( | |
| 371 self.command_name, self.proj_id_handler, self.headers, self.debug, | |
| 372 self.logger, self.bucket_storage_uri_class, bulk_uris, | |
| 373 self.recursion_requested) | |
| 374 except CommandException as e: | |
| 375 # NameExpansionIterator will complain if there are no URIs, but we don't | |
| 376 # want to throw an error if we handled bucket URIs. | |
| 377 if e.reason == 'No URIs matched': | |
| 378 return 0 | |
| 379 else: | |
| 380 raise e | |
| 381 | |
| 382 self.everything_set_okay = True | 343 self.everything_set_okay = True |
| 383 self.Apply(_ApplyAclChangesWrapper, | 344 self.ApplyAclFunc(_ApplyAclChangesWrapper, _ApplyExceptionHandler, |
| 384 name_expansion_iterator, | 345 self.args) |
| 385 _ApplyExceptionHandler) | |
| 386 if not self.everything_set_okay: | 346 if not self.everything_set_okay: |
| 387 raise CommandException('ACLs for some objects could not be set.') | 347 raise CommandException('ACLs for some objects could not be set.') |
| 388 | 348 |
| 389 @Retry(GSResponseError, tries=3, timeout_secs=1) | 349 @Retry(ServiceException, tries=3, timeout_secs=1) |
| 390 def ApplyAclChanges(self, uri_or_expansion_result): | 350 def ApplyAclChanges(self, name_expansion_result, thread_state=None): |
| 391 """Applies the changes in self.changes to the provided URI.""" | 351 """Applies the changes in self.changes to the provided URL. |
| 392 if isinstance(uri_or_expansion_result, name_expansion.NameExpansionResult): | 352 |
| 393 uri = self.suri_builder.StorageUri( | 353 Args: |
| 394 uri_or_expansion_result.expanded_uri_str) | 354 name_expansion_result: NameExpansionResult describing the target object. |
| 355 thread_state: If present, gsutil Cloud API instance to apply the changes. |
| 356 """ |
| 357 if thread_state: |
| 358 gsutil_api = thread_state |
| 395 else: | 359 else: |
| 396 uri = uri_or_expansion_result | 360 gsutil_api = self.gsutil_api |
| 397 | 361 |
| 398 try: | 362 url = name_expansion_result.expanded_storage_url |
| 399 current_acl = uri.get_acl() | 363 |
| 400 except GSResponseError as e: | 364 if url.IsBucket(): |
| 401 if (e.code == 'AccessDenied' and e.reason == 'Forbidden' | 365 bucket = gsutil_api.GetBucket(url.bucket_name, provider=url.scheme, |
| 402 and e.status == 403): | 366 fields=['acl', 'metageneration']) |
| 403 self._WarnServiceAccounts() | 367 current_acl = bucket.acl |
| 404 self.logger.warning('Failed to set acl for {0}: {1}' | 368 elif url.IsObject(): |
| 405 .format(uri, e.reason)) | 369 gcs_object = gsutil_api.GetObjectMetadata( |
| 370 url.bucket_name, url.object_name, provider=url.scheme, |
| 371 generation=url.generation, |
| 372 fields=['acl', 'generation', 'metageneration']) |
| 373 current_acl = gcs_object.acl |
| 374 if not current_acl: |
| 375 self._WarnServiceAccounts() |
| 376 self.logger.warning('Failed to set acl for %s. Please ensure you have ' |
| 377 'OWNER-role access to this resource.', url) |
| 406 return | 378 return |
| 407 | 379 |
| 408 modification_count = 0 | 380 modification_count = 0 |
| 409 for change in self.changes: | 381 for change in self.changes: |
| 410 modification_count += change.Execute(uri, current_acl, self.logger) | 382 modification_count += change.Execute(url, current_acl, 'acl', self.logger) |
| 411 if modification_count == 0: | 383 if modification_count == 0: |
| 412 self.logger.info('No changes to {0}'.format(uri)) | 384 self.logger.info('No changes to %s', url) |
| 413 return | 385 return |
| 414 | 386 |
| 415 # TODO: Remove the concept of forcing when boto provides access to | 387 try: |
| 416 # bucket generation and metageneration. | 388 if url.IsBucket(): |
| 417 headers = dict(self.headers) | 389 preconditions = Preconditions(meta_gen_match=bucket.metageneration) |
| 418 force = uri.names_bucket() | 390 bucket_metadata = apitools_messages.Bucket(acl=current_acl) |
| 419 if not force: | 391 gsutil_api.PatchBucket(url.bucket_name, bucket_metadata, |
| 420 key = uri.get_key() | 392 preconditions=preconditions, |
| 421 headers['x-goog-if-generation-match'] = key.generation | 393 provider=url.scheme, fields=['id']) |
| 422 headers['x-goog-if-metageneration-match'] = key.metageneration | 394 else: # Object |
| 395 preconditions = Preconditions(gen_match=gcs_object.generation, |
| 396 meta_gen_match=gcs_object.metageneration) |
| 423 | 397 |
| 424 # If this fails because of a precondition, it will raise a | 398 object_metadata = apitools_messages.Object(acl=current_acl) |
| 425 # GSResponseError for @Retry to handle. | 399 gsutil_api.PatchObjectMetadata( |
| 426 try: | 400 url.bucket_name, url.object_name, object_metadata, |
| 427 uri.set_acl(current_acl, uri.object_name, False, headers) | 401 preconditions=preconditions, provider=url.scheme, |
| 428 except GSResponseError as e: | 402 generation=url.generation) |
| 403 except BadRequestException as e: |
| 429 # Don't retry on bad requests, e.g. invalid email address. | 404 # Don't retry on bad requests, e.g. invalid email address. |
| 430 if getattr(e, 'status', None) == 400: | 405 raise CommandException('Received bad request from server: %s' % str(e)) |
| 431 raise CommandException('Received bad request from server: %s' % str(e)) | |
| 432 raise | |
| 433 self.logger.info('Updated ACL on {0}'.format(uri)) | |
| 434 | 406 |
| 435 # Command entry point. | 407 self.logger.info('Updated ACL on %s', url) |
| 408 |
| 436 def RunCommand(self): | 409 def RunCommand(self): |
| 410 """Command entry point for the acl command.""" |
| 437 action_subcommand = self.args.pop(0) | 411 action_subcommand = self.args.pop(0) |
| 438 (self.sub_opts, self.args) = getopt.getopt(self.args, | 412 self.sub_opts, self.args = getopt.getopt( |
| 439 self.command_spec[SUPPORTED_SUB_ARGS]) | 413 self.args, self.command_spec.supported_sub_args) |
| 440 self.CheckArguments() | 414 self.CheckArguments() |
| 415 self.def_acl = False |
| 441 if action_subcommand == 'get': | 416 if action_subcommand == 'get': |
| 442 func = self._GetAcl | 417 self.GetAndPrintAcl(self.args[0]) |
| 443 elif action_subcommand == 'set': | 418 elif action_subcommand == 'set': |
| 444 func = self._SetAcl | 419 self._SetAcl() |
| 445 elif action_subcommand in ('ch', 'change'): | 420 elif action_subcommand in ('ch', 'change'): |
| 446 func = self._ChAcl | 421 self._ChAcl() |
| 447 else: | 422 else: |
| 448 raise CommandException(('Invalid subcommand "%s" for the %s command.\n' | 423 raise CommandException(('Invalid subcommand "%s" for the %s command.\n' |
| 449 'See "gsutil help acl".') % | 424 'See "gsutil help acl".') % |
| 450 (action_subcommand, self.command_name)) | 425 (action_subcommand, self.command_name)) |
| 451 | 426 |
| 452 func() | |
| 453 return 0 | 427 return 0 |
| OLD | NEW |