Source code for stacker.actions.destroy
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import logging
from .base import BaseAction, plan, build_walker
from .base import STACK_POLL_TIME
from ..exceptions import StackDoesNotExist
from stacker.hooks.utils import handle_hooks
from ..status import (
CompleteStatus,
SubmittedStatus,
PENDING,
SUBMITTED,
INTERRUPTED
)
from ..status import StackDoesNotExist as StackDoesNotExistStatus
logger = logging.getLogger(__name__)
DestroyedStatus = CompleteStatus("stack destroyed")
DestroyingStatus = SubmittedStatus("submitted for destruction")
[docs]class Action(BaseAction):
"""Responsible for destroying CloudFormation stacks.
Generates a destruction plan based on stack dependencies. Stack
dependencies are reversed from the build action. For example, if a Stack B
requires Stack A during build, during destroy Stack A requires Stack B be
destroyed first.
The plan defaults to printing an outline of what will be destroyed. If
forced to execute, each stack will get destroyed in order.
"""
def _generate_plan(self, tail=False):
return plan(
description="Destroy stacks",
stack_action=self._destroy_stack,
tail=self._tail_stack if tail else None,
context=self.context,
reverse=True)
def _destroy_stack(self, stack, **kwargs):
old_status = kwargs.get("status")
wait_time = 0 if old_status is PENDING else STACK_POLL_TIME
if self.cancel.wait(wait_time):
return INTERRUPTED
provider = self.build_provider(stack)
try:
provider_stack = provider.get_stack(stack.fqn)
except StackDoesNotExist:
logger.debug("Stack %s does not exist.", stack.fqn)
# Once the stack has been destroyed, it doesn't exist. If the
# status of the step was SUBMITTED, we know we just deleted it,
# otherwise it should be skipped
if kwargs.get("status", None) == SUBMITTED:
return DestroyedStatus
else:
return StackDoesNotExistStatus()
logger.debug(
"Stack %s provider status: %s",
provider.get_stack_name(provider_stack),
provider.get_stack_status(provider_stack),
)
if provider.is_stack_destroyed(provider_stack):
return DestroyedStatus
elif provider.is_stack_in_progress(provider_stack):
return DestroyingStatus
else:
logger.debug("Destroying stack: %s", stack.fqn)
provider.destroy_stack(provider_stack)
return DestroyingStatus
[docs] def pre_run(self, outline=False, *args, **kwargs):
"""Any steps that need to be taken prior to running the action."""
pre_destroy = self.context.config.pre_destroy
if not outline and pre_destroy:
handle_hooks(
stage="pre_destroy",
hooks=pre_destroy,
provider=self.provider,
context=self.context)
[docs] def run(self, force, concurrency=0, tail=False, *args, **kwargs):
plan = self._generate_plan(tail=tail)
if not plan.keys():
logger.warn('WARNING: No stacks detected (error in config?)')
if force:
# need to generate a new plan to log since the outline sets the
# steps to COMPLETE in order to log them
plan.outline(logging.DEBUG)
walker = build_walker(concurrency)
plan.execute(walker)
else:
plan.outline(message="To execute this plan, run with \"--force\" "
"flag.")
[docs] def post_run(self, outline=False, *args, **kwargs):
"""Any steps that need to be taken after running the action."""
post_destroy = self.context.config.post_destroy
if not outline and post_destroy:
handle_hooks(
stage="post_destroy",
hooks=post_destroy,
provider=self.provider,
context=self.context)