AWS recently released the ability to protect stacks against termination. Currently this functionality is provided by the protect stack config, however this check is only being done client side by sceptre, leaving the stack open to manual deletion.
Should stack protection also be added to the lock-stack command? Currently that command is only protecting against updates.
I had this same use-case, and used this:
hooks:
after_create:
# enable termination protection so that we don't change the name-servers and therefore have to wait 2 days for DNS update
- !cmd aws cloudformation update-termination-protection --enable-termination-protection --stack-name microservice-preprod-hosted-zone
Yup so feel like this could be implemented apart of lock/unlock stack. Reluctant putting this into stack config - as pushes config to being more "stateful". e.g. what happens when you remove "protection" from config, but its already enabled on the stack...
aws cloudformation update-termination-protection --enable-termination-protection --stack-name . Can this can be implemented using sceptre for entire stack on cloudformation.
We don't use the lock feature, but having a terminate protecting config option is definitely useful.
The special case with removal of the terminate protection config can be handled by:
I would use the hook method but the only issue I have with it atm is that hooks don't have access to the stack name, so the name of the stack needs to be hardcoded in the hook.
If it helps anyone, I've implemented a simple custom hook for this. The nice thing for me is that it performs the operation for the current stack - no need to reference the underlying CloudFormation stack, it will be computed by the hook itself. So I can reuse exactly the same _after_create_ / _before_delete_ calls in all of my stacks, can change _project_code_ without any need to update my code as well as the name of my stack (in Spectre).
Here's the code:
# config/dev/db.yaml
hooks:
after_create:
- !set_stack_termination_protection 'enabled'
before_delete:
- !set_stack_termination_protection 'disabled'
# hooks/stacks.py
from sceptre.hooks import Hook
from sceptre.environment import Environment
class SetStackTerminationProtection(Hook):
ALLOWED_ARG_VALUES = ['enabled','disabled']
def __init__(self, *args, **kwargs):
super(SetStackTerminationProtection, self).__init__(*args, **kwargs)
def run(self):
argument = (self.argument if self.argument else '').lower()
assert argument in self.ALLOWED_ARG_VALUES, \
"As the argument for !set_stack_termination_protection, please choose one of {0}".format(self.ALLOWED_ARG_VALUES)
environment = Environment(self.environment_config.sceptre_dir, self.environment_config.environment_path)
stack = environment.stacks[self.stack_config.name]
cf_stack_name = stack.external_name
enable = argument == 'enabled'
self.logger.info(
"Setting termination protection of stack '%s' to '%s'",
cf_stack_name, argument)
self.connection_manager.call('cloudformation', 'update_termination_protection',
kwargs={
'StackName': cf_stack_name,
'EnableTerminationProtection': enable
})
cheers, haven't realized the stack name is exposed under self.stack_config.name !
Yeah, I'm just beginning with Spectre (love it so far) and I found that code snippet somewhere around forums / issues discussions.
Thanks for that @sustacek ! It really helped.
However we use var values in our template for the stack name and your code creates a new environment which doesn't seem to take them into account? Sceptre actually gives hooks information about the stack they're running on via self now, so I was able to just use that.
Here's our updated code in case it helps anyone:
# hooks/stacks.py
from sceptre.hooks import Hook
class SetStackTerminationProtection(Hook):
ALLOWED_ARG_VALUES = ['enabled','disabled']
def __init__(self, *args, **kwargs):
super(SetStackTerminationProtection, self).__init__(*args, **kwargs)
def run(self):
argument = (self.argument if self.argument else '').lower()
assert argument in self.ALLOWED_ARG_VALUES, \
"As the argument for !set_stack_termination_protection, please choose one of {0}".format(self.ALLOWED_ARG_VALUES)
cf_stack_name = self.stack_config["stack_name"]
enable = argument == 'enabled'
self.logger.info(
"Setting termination protection of stack '%s' to '%s'",
cf_stack_name, argument)
self.connection_manager.call('cloudformation', 'update_termination_protection',
kwargs={
'StackName': cf_stack_name,
'EnableTerminationProtection': enable
})
@jordie23 what version of sceptre did that work for you? Keen to know in order to resolve a same use case as described here: https://github.com/cloudreach/sceptre/issues/410
@craighurley Sceptre 1.3.4
I'm new to sceptre, so I've just realised we define stack_name inside our stack config:
https://sceptre.cloudreach.com/latest/docs/stack_config.html#stack_name
@jordie23 - would you be willing to open a PR with your code as an addition to contrib/?
PR https://github.com/cloudreach/sceptre/pull/498 is for sceptre V1, A port of that hook to sceptre V2 is here: https://github.com/zaro0508/sceptre-stack-termination-protection-hook
@ngfgrant is there anything else to do in this issue? Hooks provide ability to set termination protection on an entire stack and sceptre has the set-policy command to support protecting stack resources, https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/protect-stack-resources.html
the termination protection hook has been moved into the sceptre org:
https://github.com/Sceptre/sceptre-stack-termination-protection-hook
Most helpful comment
If it helps anyone, I've implemented a simple custom hook for this. The nice thing for me is that it performs the operation for the current stack - no need to reference the underlying CloudFormation stack, it will be computed by the hook itself. So I can reuse exactly the same _after_create_ / _before_delete_ calls in all of my stacks, can change _project_code_ without any need to update my code as well as the name of my stack (in Spectre).
Here's the code: