Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(461)

Side by Side Diff: third_party/gsutil/gslib/commands/rm.py

Issue 1380943003: Roll version of gsutil to 4.15. (Closed) Base URL: https://github.com/catapult-project/catapult.git@master
Patch Set: rebase Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « third_party/gsutil/gslib/commands/rb.py ('k') | third_party/gsutil/gslib/commands/rsync.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # -*- coding: utf-8 -*- 1 # -*- coding: utf-8 -*-
2 # Copyright 2011 Google Inc. All Rights Reserved. 2 # Copyright 2011 Google Inc. All Rights Reserved.
3 # 3 #
4 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # 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.
6 # You may obtain a copy of the License at 6 # You may obtain a copy of the License at
7 # 7 #
8 # http://www.apache.org/licenses/LICENSE-2.0 8 # http://www.apache.org/licenses/LICENSE-2.0
9 # 9 #
10 # Unless required by applicable law or agreed to in writing, software 10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, 11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and 13 # See the License for the specific language governing permissions and
14 # limitations under the License. 14 # limitations under the License.
15 """Implementation of Unix-like rm command for cloud storage providers.""" 15 """Implementation of Unix-like rm command for cloud storage providers."""
16 16
17 from __future__ import absolute_import 17 from __future__ import absolute_import
18 18
19 from gslib.cloud_api import BucketNotFoundException
19 from gslib.cloud_api import NotEmptyException 20 from gslib.cloud_api import NotEmptyException
21 from gslib.cloud_api import NotFoundException
20 from gslib.cloud_api import ServiceException 22 from gslib.cloud_api import ServiceException
21 from gslib.command import Command 23 from gslib.command import Command
22 from gslib.command import GetFailureCount 24 from gslib.command import GetFailureCount
23 from gslib.command import ResetFailureCount 25 from gslib.command import ResetFailureCount
24 from gslib.command_argument import CommandArgument 26 from gslib.command_argument import CommandArgument
25 from gslib.cs_api_map import ApiSelector 27 from gslib.cs_api_map import ApiSelector
26 from gslib.exception import CommandException 28 from gslib.exception import CommandException
27 from gslib.name_expansion import NameExpansionIterator 29 from gslib.name_expansion import NameExpansionIterator
28 from gslib.storage_url import StorageUrlFromString 30 from gslib.storage_url import StorageUrlFromString
29 from gslib.translation_helper import PreconditionsFromHeaders 31 from gslib.translation_helper import PreconditionsFromHeaders
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after
132 flag implies the -a flag and will delete all object versions. 134 flag implies the -a flag and will delete all object versions.
133 135
134 -a Delete all versions of an object. 136 -a Delete all versions of an object.
135 """) 137 """)
136 138
137 139
138 def _RemoveExceptionHandler(cls, e): 140 def _RemoveExceptionHandler(cls, e):
139 """Simple exception handler to allow post-completion status.""" 141 """Simple exception handler to allow post-completion status."""
140 if not cls.continue_on_error: 142 if not cls.continue_on_error:
141 cls.logger.error(str(e)) 143 cls.logger.error(str(e))
142 cls.everything_removed_okay = False 144 # TODO: Use shared state to track missing bucket names when we get a
145 # BucketNotFoundException. Then improve bucket removal logic and exception
146 # messages.
147 if isinstance(e, BucketNotFoundException):
148 cls.bucket_not_found_count += 1
149 cls.logger.error(str(e))
150 else:
151 cls.op_failure_count += 1
143 152
144 153
145 # pylint: disable=unused-argument 154 # pylint: disable=unused-argument
146 def _RemoveFoldersExceptionHandler(cls, e): 155 def _RemoveFoldersExceptionHandler(cls, e):
147 """When removing folders, we don't mind if none exist.""" 156 """When removing folders, we don't mind if none exist."""
148 if (isinstance(e, CommandException.__class__) and 157 if (isinstance(e, CommandException.__class__) and
149 'No URLs matched' in e.message): 158 'No URLs matched' in e.message) or isinstance(e, NotFoundException):
150 pass 159 pass
151 else: 160 else:
152 raise e 161 raise e
153 162
154 163
155 def _RemoveFuncWrapper(cls, name_expansion_result, thread_state=None): 164 def _RemoveFuncWrapper(cls, name_expansion_result, thread_state=None):
156 cls.RemoveFunc(name_expansion_result, thread_state=thread_state) 165 cls.RemoveFunc(name_expansion_result, thread_state=thread_state)
157 166
158 167
159 class RmCommand(Command): 168 class RmCommand(Command):
(...skipping 23 matching lines...) Expand all
183 help_type='command_help', 192 help_type='command_help',
184 help_one_line_summary='Remove objects', 193 help_one_line_summary='Remove objects',
185 help_text=_DETAILED_HELP_TEXT, 194 help_text=_DETAILED_HELP_TEXT,
186 subcommand_help_text={}, 195 subcommand_help_text={},
187 ) 196 )
188 197
189 def RunCommand(self): 198 def RunCommand(self):
190 """Command entry point for the rm command.""" 199 """Command entry point for the rm command."""
191 # self.recursion_requested is initialized in command.py (so it can be 200 # self.recursion_requested is initialized in command.py (so it can be
192 # checked in parent class for all commands). 201 # checked in parent class for all commands).
193 self.continue_on_error = False 202 self.continue_on_error = self.parallel_operations
194 self.read_args_from_stdin = False 203 self.read_args_from_stdin = False
195 self.all_versions = False 204 self.all_versions = False
196 if self.sub_opts: 205 if self.sub_opts:
197 for o, unused_a in self.sub_opts: 206 for o, unused_a in self.sub_opts:
198 if o == '-a': 207 if o == '-a':
199 self.all_versions = True 208 self.all_versions = True
200 elif o == '-f': 209 elif o == '-f':
201 self.continue_on_error = True 210 self.continue_on_error = True
202 elif o == '-I': 211 elif o == '-I':
203 self.read_args_from_stdin = True 212 self.read_args_from_stdin = True
204 elif o == '-r' or o == '-R': 213 elif o == '-r' or o == '-R':
205 self.recursion_requested = True 214 self.recursion_requested = True
206 self.all_versions = True 215 self.all_versions = True
207 216
208 if self.read_args_from_stdin: 217 if self.read_args_from_stdin:
209 if self.args: 218 if self.args:
210 raise CommandException('No arguments allowed with the -I flag.') 219 raise CommandException('No arguments allowed with the -I flag.')
211 url_strs = StdinIterator() 220 url_strs = StdinIterator()
212 else: 221 else:
213 if not self.args: 222 if not self.args:
214 raise CommandException('The rm command (without -I) expects at ' 223 raise CommandException('The rm command (without -I) expects at '
215 'least one URL.') 224 'least one URL.')
216 url_strs = self.args 225 url_strs = self.args
217 226
227 # Tracks if any deletes failed.
228 self.op_failure_count = 0
229
230 # Tracks if any buckets were missing.
231 self.bucket_not_found_count = 0
232
218 bucket_urls_to_delete = [] 233 bucket_urls_to_delete = []
219 bucket_strings_to_delete = [] 234 bucket_strings_to_delete = []
220 if self.recursion_requested: 235 if self.recursion_requested:
221 bucket_fields = ['id'] 236 bucket_fields = ['id']
222 for url_str in url_strs: 237 for url_str in url_strs:
223 url = StorageUrlFromString(url_str) 238 url = StorageUrlFromString(url_str)
224 if url.IsBucket() or url.IsProvider(): 239 if url.IsBucket() or url.IsProvider():
225 for blr in self.WildcardIterator(url_str).IterBuckets( 240 for blr in self.WildcardIterator(url_str).IterBuckets(
226 bucket_fields=bucket_fields): 241 bucket_fields=bucket_fields):
227 bucket_urls_to_delete.append(blr.storage_url) 242 bucket_urls_to_delete.append(blr.storage_url)
228 bucket_strings_to_delete.append(url_str) 243 bucket_strings_to_delete.append(url_str)
229 244
230 self.preconditions = PreconditionsFromHeaders(self.headers or {}) 245 self.preconditions = PreconditionsFromHeaders(self.headers or {})
231 246
232 # Used to track if any files failed to be removed.
233 self.everything_removed_okay = True
234
235 try: 247 try:
236 # Expand wildcards, dirs, buckets, and bucket subdirs in URLs. 248 # Expand wildcards, dirs, buckets, and bucket subdirs in URLs.
237 name_expansion_iterator = NameExpansionIterator( 249 name_expansion_iterator = NameExpansionIterator(
238 self.command_name, self.debug, self.logger, self.gsutil_api, 250 self.command_name, self.debug, self.logger, self.gsutil_api,
239 url_strs, self.recursion_requested, project_id=self.project_id, 251 url_strs, self.recursion_requested, project_id=self.project_id,
240 all_versions=self.all_versions, 252 all_versions=self.all_versions,
241 continue_on_error=self.continue_on_error or self.parallel_operations) 253 continue_on_error=self.continue_on_error or self.parallel_operations)
242 254
243 # Perform remove requests in parallel (-m) mode, if requested, using 255 # Perform remove requests in parallel (-m) mode, if requested, using
244 # configured number of parallel processes and threads. Otherwise, 256 # configured number of parallel processes and threads. Otherwise,
245 # perform requests with sequential function calls in current process. 257 # perform requests with sequential function calls in current process.
246 self.Apply(_RemoveFuncWrapper, name_expansion_iterator, 258 self.Apply(_RemoveFuncWrapper, name_expansion_iterator,
247 _RemoveExceptionHandler, 259 _RemoveExceptionHandler,
248 fail_on_error=(not self.continue_on_error)) 260 fail_on_error=(not self.continue_on_error),
261 shared_attrs=['op_failure_count', 'bucket_not_found_count'])
249 262
250 # Assuming the bucket has versioning enabled, url's that don't map to 263 # Assuming the bucket has versioning enabled, url's that don't map to
251 # objects should throw an error even with all_versions, since the prior 264 # objects should throw an error even with all_versions, since the prior
252 # round of deletes only sends objects to a history table. 265 # round of deletes only sends objects to a history table.
253 # This assumption that rm -a is only called for versioned buckets should be 266 # This assumption that rm -a is only called for versioned buckets should be
254 # corrected, but the fix is non-trivial. 267 # corrected, but the fix is non-trivial.
255 except CommandException as e: 268 except CommandException as e:
256 # Don't raise if there are buckets to delete -- it's valid to say: 269 # Don't raise if there are buckets to delete -- it's valid to say:
257 # gsutil rm -r gs://some_bucket 270 # gsutil rm -r gs://some_bucket
258 # if the bucket is empty. 271 # if the bucket is empty.
259 if not bucket_urls_to_delete and not self.continue_on_error: 272 if not bucket_urls_to_delete and not self.continue_on_error:
260 raise 273 raise
261 # Reset the failure count if we failed due to an empty bucket that we're 274 # Reset the failure count if we failed due to an empty bucket that we're
262 # going to delete. 275 # going to delete.
263 msg = 'No URLs matched: ' 276 msg = 'No URLs matched: '
264 if msg in str(e): 277 if msg in str(e):
265 parts = str(e).split(msg) 278 parts = str(e).split(msg)
266 if len(parts) == 2 and parts[1] in bucket_strings_to_delete: 279 if len(parts) == 2 and parts[1] in bucket_strings_to_delete:
267 ResetFailureCount() 280 ResetFailureCount()
281 else:
282 raise
268 except ServiceException, e: 283 except ServiceException, e:
269 if not self.continue_on_error: 284 if not self.continue_on_error:
270 raise 285 raise
271 286
272 if not self.everything_removed_okay and not self.continue_on_error: 287 if self.bucket_not_found_count:
288 raise CommandException('Encountered non-existent bucket during listing')
289
290 if self.op_failure_count and not self.continue_on_error:
273 raise CommandException('Some files could not be removed.') 291 raise CommandException('Some files could not be removed.')
274 292
275 # If this was a gsutil rm -r command covering any bucket subdirs, 293 # If this was a gsutil rm -r command covering any bucket subdirs,
276 # remove any dir_$folder$ objects (which are created by various web UI 294 # remove any dir_$folder$ objects (which are created by various web UI
277 # tools to simulate folders). 295 # tools to simulate folders).
278 if self.recursion_requested: 296 if self.recursion_requested:
279 had_previous_failures = GetFailureCount() > 0 297 had_previous_failures = GetFailureCount() > 0
280 folder_object_wildcards = [] 298 folder_object_wildcards = []
281 for url_str in url_strs: 299 for url_str in url_strs:
282 url = StorageUrlFromString(url_str) 300 url = StorageUrlFromString(url_str)
(...skipping 22 matching lines...) Expand all
305 # Now that all data has been deleted, delete any bucket URLs. 323 # Now that all data has been deleted, delete any bucket URLs.
306 for url in bucket_urls_to_delete: 324 for url in bucket_urls_to_delete:
307 self.logger.info('Removing %s...', url) 325 self.logger.info('Removing %s...', url)
308 326
309 @Retry(NotEmptyException, tries=3, timeout_secs=1) 327 @Retry(NotEmptyException, tries=3, timeout_secs=1)
310 def BucketDeleteWithRetry(): 328 def BucketDeleteWithRetry():
311 self.gsutil_api.DeleteBucket(url.bucket_name, provider=url.scheme) 329 self.gsutil_api.DeleteBucket(url.bucket_name, provider=url.scheme)
312 330
313 BucketDeleteWithRetry() 331 BucketDeleteWithRetry()
314 332
333 if self.op_failure_count:
334 plural_str = 's' if self.op_failure_count else ''
335 raise CommandException('%d file%s/object%s could not be removed.' % (
336 self.op_failure_count, plural_str, plural_str))
337
315 return 0 338 return 0
316 339
317 def RemoveFunc(self, name_expansion_result, thread_state=None): 340 def RemoveFunc(self, name_expansion_result, thread_state=None):
318 gsutil_api = GetCloudApiInstance(self, thread_state=thread_state) 341 gsutil_api = GetCloudApiInstance(self, thread_state=thread_state)
319 342
320 exp_src_url = name_expansion_result.expanded_storage_url 343 exp_src_url = name_expansion_result.expanded_storage_url
321 self.logger.info('Removing %s...', exp_src_url) 344 self.logger.info('Removing %s...', exp_src_url)
322 gsutil_api.DeleteObject( 345 gsutil_api.DeleteObject(
323 exp_src_url.bucket_name, exp_src_url.object_name, 346 exp_src_url.bucket_name, exp_src_url.object_name,
324 preconditions=self.preconditions, generation=exp_src_url.generation, 347 preconditions=self.preconditions, generation=exp_src_url.generation,
325 provider=exp_src_url.scheme) 348 provider=exp_src_url.scheme)
326 349
OLDNEW
« no previous file with comments | « third_party/gsutil/gslib/commands/rb.py ('k') | third_party/gsutil/gslib/commands/rsync.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698