File: //snap/google-cloud-cli/396/lib/surface/storage/rm.py
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implementation of rm command for deleting resources."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import collections
from googlecloudsdk.api_lib.storage import cloud_api
from googlecloudsdk.api_lib.storage import errors as api_errors
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.storage import flags
from googlecloudsdk.command_lib.storage import folder_util
from googlecloudsdk.command_lib.storage import name_expansion
from googlecloudsdk.command_lib.storage import plurality_checkable_iterator
from googlecloudsdk.command_lib.storage import rm_command_util
from googlecloudsdk.command_lib.storage import stdin_iterator
from googlecloudsdk.command_lib.storage import user_request_args_factory
from googlecloudsdk.command_lib.storage.tasks import task_executor
from googlecloudsdk.command_lib.storage.tasks import task_graph_executor
from googlecloudsdk.command_lib.storage.tasks import task_status
from googlecloudsdk.command_lib.storage.tasks.rm import delete_task_iterator_factory
from googlecloudsdk.core import log
@base.UniverseCompatible
class Rm(base.Command):
"""Delete objects and buckets."""
detailed_help = {
'DESCRIPTION':
"""
Delete objects and buckets.
""",
'EXAMPLES':
"""
The following command deletes a Cloud Storage object named ``my-object''
from the bucket ``my-bucket'':
$ {command} gs://my-bucket/my-object
The following command deletes all objects directly within the directory
``my-dir'' but no objects within subdirectories:
$ {command} gs://my-bucket/my-dir/*
The following command deletes all objects and subdirectories within the
directory ``my-dir'':
$ {command} gs://my-bucket/my-dir/**
Note that for buckets that contain
[versioned objects](https://cloud.google.com/storage/docs/object-versioning),
the above command only affects live versions. Use the `--recursive` flag
instead to delete all versions.
The following command deletes all versions of all resources in
``my-bucket'' and then deletes the bucket.
$ {command} --recursive gs://my-bucket/
The following command deletes all text files in the top-level of
``my-bucket'', but not text files in subdirectories:
$ {command} -recursive gs://my-bucket/*.txt
The following command deletes one wildcard expression per line passed
in by stdin:
$ some_program | {command} -I
""",
}
@classmethod
def Args(cls, parser):
parser.add_argument(
'urls',
nargs='*',
help='The URLs of the resources to delete.')
parser.add_argument(
'--recursive',
'-R',
'-r',
action='store_true',
help=(
'Recursively delete the contents of buckets or directories that'
' match the path expression.'
' By default, this will delete managed folders as well.'
' If the path is set to a bucket, like'
" ``gs://bucket'', the bucket is also deleted. This option"
' implies the `--all-versions` option. If you want to delete only'
" live object versions, use the ``**'' wildcard instead."
),
)
parser.add_argument(
'--all-versions',
'-a',
action='store_true',
help='Delete all'
' [versions](https://cloud.google.com/storage/docs/object-versioning)'
' of an object.')
parser.add_argument(
'--exclude-managed-folders',
action='store_true',
default=False,
help=(
'Excludes managed folders from command operations. By default'
' gcloud storage includes managed folders in recursive removals.'
' Please note that this flag would not be applicable for'
' hierarchical namespace buckets as we always list managed folders'
' for these buckets.'
),
)
flags.add_additional_headers_flag(parser)
flags.add_continue_on_error_flag(parser)
flags.add_precondition_flags(parser)
flags.add_read_paths_from_stdin_flag(parser)
def Run(self, args):
if args.recursive:
bucket_setting = name_expansion.BucketSetting.YES
object_state = cloud_api.ObjectState.LIVE_AND_NONCURRENT
recursion_setting = name_expansion.RecursionSetting.YES
else:
bucket_setting = name_expansion.BucketSetting.NO
object_state = flags.get_object_state_from_flags(args)
recursion_setting = name_expansion.RecursionSetting.NO_WITH_WARNING
should_perform_managed_folder_operations = (
args.recursive and not args.exclude_managed_folders
)
url_found_match_tracker = collections.OrderedDict()
# We need to ensure that if we are going to do additional lookups for
# Folders or Managed Folders, then we do not throw an error for unmatched
# URLs at this stage.
# If the command is run with recursive option, then for sure we will do
# a Folders look up at a minimum and hence we will let that lookup throw
# an error if we are not going to include managed folders or fallback to the
# managed folders lookup stage.
# If the command is run without a recursive option, then for sure we will
# not do a Folders lookup and we will also not do a Managed Folders lookup
# hence, this becomes the right place to throw an error.
name_expansion_iterator = name_expansion.NameExpansionIterator(
stdin_iterator.get_urls_iterable(args.urls, args.read_paths_from_stdin),
fields_scope=cloud_api.FieldsScope.SHORT,
include_buckets=bucket_setting,
managed_folder_setting=folder_util.ManagedFolderSetting.DO_NOT_LIST,
folder_setting=folder_util.FolderSetting.LIST_AS_PREFIXES,
object_state=object_state,
raise_error_for_unmatched_urls=not args.recursive,
recursion_requested=recursion_setting,
url_found_match_tracker=url_found_match_tracker,
)
user_request_args = (
user_request_args_factory.get_user_request_args_from_command_args(args))
task_status_queue = task_graph_executor.multiprocessing_context.Queue()
task_iterator_factory = (
delete_task_iterator_factory.DeleteTaskIteratorFactory(
name_expansion_iterator,
task_status_queue=task_status_queue,
user_request_args=user_request_args))
log.status.Print('Removing objects:')
object_exit_code = task_executor.execute_tasks(
task_iterator_factory.object_iterator(),
parallelizable=True,
task_status_queue=task_status_queue,
progress_manager_args=task_status.ProgressManagerArgs(
increment_type=task_status.IncrementType.INTEGER, manifest_path=None
),
continue_on_error=args.continue_on_error,
)
if args.recursive:
folder_expansion_iterator = name_expansion.NameExpansionIterator(
args.urls,
folder_setting=folder_util.FolderSetting.LIST_AS_FOLDERS,
raise_error_for_unmatched_urls=not should_perform_managed_folder_operations,
recursion_requested=recursion_setting,
url_found_match_tracker=url_found_match_tracker,
)
try:
folder_exit_code = rm_command_util.remove_folders(
folder_expansion_iterator,
task_status_queue,
verbose=True,
)
except api_errors.GcsApiError as error:
if error.payload.status_code != 403:
# Avoids unexpectedly escalating permissions.
raise
log.warning('Unable to delete folders due to missing permissions.')
folder_exit_code = 0
else:
folder_exit_code = 0
if should_perform_managed_folder_operations:
managed_folder_expansion_iterator = name_expansion.NameExpansionIterator(
args.urls,
managed_folder_setting=folder_util.ManagedFolderSetting.LIST_WITHOUT_OBJECTS,
raise_error_for_unmatched_urls=True,
# `rm` defaults to including managed folders, but this will raise a
# precondition error if the command targets a non-UBLA bucket. These
# errors should be silenced.
raise_managed_folder_precondition_errors=False,
recursion_requested=name_expansion.RecursionSetting.YES,
url_found_match_tracker=url_found_match_tracker,
)
try:
managed_folder_exit_code = rm_command_util.remove_managed_folders(
args,
managed_folder_expansion_iterator,
task_status_queue,
verbose=True,
)
except api_errors.GcsApiError as error:
if error.payload.status_code != 403:
# Avoids unexpectedly escalating permissions.
raise
log.warning(
'Unable to delete managed folders due to missing permissions.'
)
managed_folder_exit_code = 0
else:
managed_folder_exit_code = 0
bucket_iterator = plurality_checkable_iterator.PluralityCheckableIterator(
task_iterator_factory.bucket_iterator()
)
# We perform the is_empty check to avoid printing unneccesary status lines.
if args.recursive and not bucket_iterator.is_empty():
log.status.Print('Removing buckets:')
bucket_exit_code = task_executor.execute_tasks(
bucket_iterator,
parallelizable=True,
task_status_queue=task_status_queue,
progress_manager_args=task_status.ProgressManagerArgs(
increment_type=task_status.IncrementType.INTEGER,
manifest_path=None,
),
continue_on_error=args.continue_on_error,
)
else:
bucket_exit_code = 0
self.exit_code = max(
object_exit_code,
managed_folder_exit_code,
folder_exit_code,
bucket_exit_code,
)