File: //snap/google-cloud-cli/396/lib/surface/compute/instances/delete.py
# -*- coding: utf-8 -*- #
# Copyright 2014 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.
"""Command for deleting instances."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.compute import base_classes
from googlecloudsdk.api_lib.compute import utils
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute import completers
from googlecloudsdk.command_lib.compute import exceptions as compute_exceptions
from googlecloudsdk.command_lib.compute.instances import flags
from googlecloudsdk.core import resources as cloud_resources
from googlecloudsdk.core.console import console_io
from six.moves import zip
_INSTANCE_DELETE_PROMPT = 'The following instances will be deleted.'
_INSTANCE_DELETE_PROMPT_DISK_ADDENDUM = """\
Any attached disks configured to be auto-deleted will be deleted unless they \
are attached to any other instances or the `--keep-disks` flag is given and \
specifies them for keeping. \
Deleting a disk is irreversible and any data on the disk will be lost."""
# During delete, graceful shutdown can take up to 60 minutes to complete, we
# are setting timeout to `70` minutes to give some space for delete operation
# to complete gracefully
_TIMEOUT_IN_SEC = 60 * 70
AUTO_DELETE_OVERRIDE_CHOICES = {
'boot': 'The first partition is reserved for the root filesystem.',
'data': 'A non-boot disk.',
'all': 'All disk types.',
}
DETAILED_HELP = {
'EXAMPLES': """
To delete an instance called 'instance-1' in the zone 'us-central-2-a', run:
$ {command} instance-1 --zone=us-central2-a
"""
}
@base.UniverseCompatible
class Delete(base.DeleteCommand):
"""Delete Compute Engine virtual machine instances.
*{command}* deletes one or more Compute Engine virtual machine
instances.
"""
@classmethod
def Args(cls, parser):
auto_delete_override = parser.add_mutually_exclusive_group()
auto_delete_override.add_argument(
'--delete-disks',
choices=AUTO_DELETE_OVERRIDE_CHOICES,
metavar='DISK_TYPE',
help="""\
The types of disks to delete with instance deletion regardless
of the disks' auto-delete configuration. When this flag is
provided, the auto-delete bits on the attached disks are
modified accordingly before the instance deletion requests are
issued. For more information on disk auto-deletion, see
[Set the auto-delete state of a zonal persistent disk](https://cloud.google.com/compute/docs/disks/modify-persistent-disk#updateautodelete)
""")
auto_delete_override.add_argument(
'--keep-disks',
choices=AUTO_DELETE_OVERRIDE_CHOICES,
metavar='DISK_TYPE',
help="""\
The types of disks to not delete with instance deletion regardless
of the disks' auto-delete configuration. When this flag is
provided, the auto-delete bits on the attached disks are
modified accordingly before the instance deletion requests are
issued. For more information on disk auto-deletion, see
[Set the auto-delete state of a zonal persistent disk](https://cloud.google.com/compute/docs/disks/modify-persistent-disk#updateautodelete)
""")
flags.INSTANCES_ARG.AddArgument(parser, operation_type='delete')
if (
cls.ReleaseTrack() == base.ReleaseTrack.ALPHA
or cls.ReleaseTrack() == base.ReleaseTrack.BETA
):
parser.add_argument(
'--no-graceful-shutdown',
action='store_true',
default=None,
help=(
'Deletes the instance immediately without gracefully shutting it'
' down. If a graceful shutdown is in progress, then the instance'
' is forcefully stopped and deleted.'
),
)
parser.display_info.AddCacheUpdater(completers.InstancesCompleter)
def GetInstances(self, refs, client):
"""Fetches instance objects corresponding to the given references."""
instance_get_requests = []
for ref in refs:
request_protobuf = client.messages.ComputeInstancesGetRequest(
**ref.AsDict())
instance_get_requests.append((client.apitools_client.instances, 'Get',
request_protobuf))
errors = []
instances = client.MakeRequests(
requests=instance_get_requests,
errors_to_collect=errors)
if errors:
utils.RaiseToolException(
errors,
error_message='Failed to fetch some instances:')
return instances
def PromptIfDisksWithoutAutoDeleteWillBeDeleted(self, disks_to_warn_for):
"""Prompts if disks with False autoDelete will be deleted.
Args:
disks_to_warn_for: list of tuple of (references to disk resources, disk
type). disk_type(str)-> either 'zonalDisk' or 'regionalDisk'.
"""
if not disks_to_warn_for:
return
prompt_list = []
for ref, disk_type in disks_to_warn_for:
prompt_list.append(
'[{0}] in [{1}]'.format(
ref.Name(), ref.zone if disk_type == 'zonalDisk' else ref.region
)
)
prompt_message = utils.ConstructList(
'The following disks are not configured to be automatically deleted '
'with instance deletion, but they will be deleted as a result of '
'this operation if they are not attached to any other instances:',
prompt_list,
)
if not console_io.PromptContinue(message=prompt_message):
raise compute_exceptions.AbortedError('Deletion aborted by user.')
def AutoDeleteMustBeChanged(self, args, disk_resource):
"""Returns True if the autoDelete property of the disk must be changed."""
if args.keep_disks == 'boot':
return disk_resource.autoDelete and disk_resource.boot
elif args.keep_disks == 'data':
return disk_resource.autoDelete and not disk_resource.boot
elif args.keep_disks == 'all':
return disk_resource.autoDelete
elif args.delete_disks == 'data':
return not disk_resource.autoDelete and not disk_resource.boot
elif args.delete_disks == 'all':
return not disk_resource.autoDelete
elif args.delete_disks == 'boot':
return not disk_resource.autoDelete and disk_resource.boot
return False
def Run(self, args):
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
client = holder.client
refs = flags.INSTANCES_ARG.ResolveAsResource(
args, holder.resources,
scope_lister=flags.GetInstanceZoneScopeLister(client))
msg = _INSTANCE_DELETE_PROMPT
if args.keep_disks != 'all':
msg += ' ' + _INSTANCE_DELETE_PROMPT_DISK_ADDENDUM
utils.PromptForDeletion(refs, scope_name='zone', prompt_title=msg)
if args.delete_disks or args.keep_disks:
instance_resources = self.GetInstances(refs, client)
disks_to_warn_for = []
set_auto_delete_requests = []
for ref, resource in zip(refs, instance_resources):
for disk in resource.disks:
# Determines whether the current disk needs to have its
# autoDelete parameter changed.
if not self.AutoDeleteMustBeChanged(args, disk):
continue
# At this point, we know that the autoDelete property of the
# disk must be changed. Since autoDelete is a boolean, we
# just negate it!
# Yay, computer science! :) :) :)
new_auto_delete = not disk.autoDelete
if new_auto_delete:
# Parse the disk as zonal and if it fails, parse it as regional.
try:
disks_to_warn_for.append((
holder.resources.Parse(
disk.source,
collection='compute.disks',
params={'zone': ref.zone},
),
'zonalDisk',
))
except cloud_resources.WrongResourceCollectionException:
disks_to_warn_for.append((
holder.resources.Parse(
disk.source,
collection='compute.regionDisks',
params={'zone': ref.zone},
),
'regionalDisk',
))
set_auto_delete_requests.append((
client.apitools_client.instances,
'SetDiskAutoDelete',
client.messages.ComputeInstancesSetDiskAutoDeleteRequest(
autoDelete=new_auto_delete,
deviceName=disk.deviceName,
instance=ref.Name(),
project=ref.project,
zone=ref.zone)))
if set_auto_delete_requests:
self.PromptIfDisksWithoutAutoDeleteWillBeDeleted(disks_to_warn_for)
errors = []
client.MakeRequests(
requests=set_auto_delete_requests,
errors_to_collect=errors,
timeout=_TIMEOUT_IN_SEC,
)
if errors:
utils.RaiseToolException(
errors,
error_message=('Some requests to change disk auto-delete '
'behavior failed:'))
delete_requests = []
for ref in refs:
if (
self.ReleaseTrack() == base.ReleaseTrack.ALPHA
or self.ReleaseTrack() == base.ReleaseTrack.BETA
):
request_protobuf = client.messages.ComputeInstancesDeleteRequest(
**ref.AsDict(), noGracefulShutdown=args.no_graceful_shutdown
)
else:
request_protobuf = client.messages.ComputeInstancesDeleteRequest(
**ref.AsDict()
)
delete_requests.append((client.apitools_client.instances, 'Delete',
request_protobuf))
return client.MakeRequests(delete_requests, timeout=_TIMEOUT_IN_SEC)
Delete.detailed_help = DETAILED_HELP