Skip to content

Commit

Permalink
[Resolves Sceptre#1179] cloudformation disable-rollback option (Scept…
Browse files Browse the repository at this point in the history
…re#1282)

Allow user to disable a cloudformation rollback on a sceptre deployment.
  • Loading branch information
zaro0508 authored Jan 10, 2023
1 parent 2d7ce6a commit 99c839a
Show file tree
Hide file tree
Showing 23 changed files with 187 additions and 8 deletions.
19 changes: 19 additions & 0 deletions docs/_source/docs/stack_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ particular Stack. The available keys are listed below.
- `notifications`_ *(optional)*
- `obsolete`_ *(optional)*
- `on_failure`_ *(optional)*
- `disable_rollback`_ *(optional)*
- `parameters`_ *(optional)*
- `protected`_ *(optional)*
- `role_arn`_ *(optional)*
Expand Down Expand Up @@ -205,6 +206,23 @@ Examples include:

``on_failure: "DELETE"``

disable_rollback
~~~~~~~~~~~~~~~~
* Resolvable: No
* Can be inherited from StackGroup: Yes
* Inheritance strategy: Overrides parent if set

This parameter describes the action taken by CloudFormation when a Stack fails
to create or update, default is False. This option can be set from the stack
config or from the Sceptre CLI commands to deploy stacks. The disable_rollback
CLI option (i.e. sceptre launch --disable-rollback) disables cloudformation
rollback globally for all stacks. This option overrides on_failure since
Cloudformation does not allow setting both on deployment. For more information
and valid values see the `AWS Documentation`_.

Examples:

``disable_rollback: "True"``

parameters
~~~~~~~~~~
Expand Down Expand Up @@ -579,6 +597,7 @@ Examples
.. _hooks: #hooks
.. _notifications: #notifications
.. _on_failure: #on-failure
.. _disable_rollback: #disable-rollback
.. _parameters: #parameters
.. _protected: #protected
.. _role_arn: #role-arn
Expand Down
10 changes: 9 additions & 1 deletion sceptre/cli/create.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import click

from typing import Optional
from sceptre.context import SceptreContext
from sceptre.cli.helpers import catch_exceptions, confirmation
from sceptre.plan.plan import SceptrePlan
Expand All @@ -10,9 +11,14 @@
@click.argument("path")
@click.argument("change-set-name", required=False)
@click.option("-y", "--yes", is_flag=True, help="Assume yes to all questions.")
@click.option(
"--disable-rollback/--enable-rollback",
default=None,
help="Disable or enable the cloudformation automatic rollback",
)
@click.pass_context
@catch_exceptions
def create_command(ctx, path, change_set_name, yes):
def create_command(ctx, path, change_set_name, yes, disable_rollback: Optional[bool]):
"""
Creates a stack for a given config PATH. Or if CHANGE_SET_NAME is specified
creates a change set for stack in PATH.
Expand All @@ -24,9 +30,11 @@ def create_command(ctx, path, change_set_name, yes):
:type change_set_name: str
:param yes: A flag to assume yes to all questions.
:type yes: bool
:param disable_rollback: A flag to disable cloudformation rollback.
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down
1 change: 1 addition & 0 deletions sceptre/cli/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def delete_command(ctx, path, change_set_name, yes):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down
2 changes: 2 additions & 0 deletions sceptre/cli/describe.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def describe_change_set(ctx, path, change_set_name, verbose):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down Expand Up @@ -66,6 +67,7 @@ def describe_policy(ctx, path):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down
1 change: 1 addition & 0 deletions sceptre/cli/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def diff_command(

context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down
2 changes: 2 additions & 0 deletions sceptre/cli/drift.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def drift_detect(ctx: Context, path: str):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down Expand Up @@ -84,6 +85,7 @@ def drift_show(ctx, path, drifted):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down
1 change: 1 addition & 0 deletions sceptre/cli/dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def dump_config(ctx, path):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
output_format=ctx.obj.get("output_format"),
Expand Down
9 changes: 8 additions & 1 deletion sceptre/cli/execute.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import click

from typing import Optional
from sceptre.context import SceptreContext
from sceptre.cli.helpers import catch_exceptions, confirmation
from sceptre.plan.plan import SceptrePlan
Expand All @@ -9,9 +10,14 @@
@click.argument("path")
@click.argument("change-set-name")
@click.option("-y", "--yes", is_flag=True, help="Assume yes to all questions.")
@click.option(
"--disable-rollback/--enable-rollback",
default=None,
help="Disable or enable the cloudformation automatic rollback",
)
@click.pass_context
@catch_exceptions
def execute_command(ctx, path, change_set_name, yes):
def execute_command(ctx, path, change_set_name, yes, disable_rollback: Optional[bool]):
"""
Executes a Change Set.
\f
Expand All @@ -25,6 +31,7 @@ def execute_command(ctx, path, change_set_name, yes):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down
14 changes: 11 additions & 3 deletions sceptre/cli/launch.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from typing import List
from typing import List, Optional

import click
from click import Context
Expand All @@ -24,9 +24,16 @@
is_flag=True,
help="If set, will delete all stacks in the command path marked as obsolete.",
)
@click.option(
"--disable-rollback/--enable-rollback",
default=False,
help="Disable or enable the cloudformation automatic rollback",
)
@click.pass_context
@catch_exceptions
def launch_command(ctx: Context, path: str, yes: bool, prune: bool):
def launch_command(
ctx: Context, path: str, yes: bool, prune: bool, disable_rollback: Optional[bool]
):
"""
Launch a Stack or StackGroup for a given config PATH. This command is intended as a catch-all
command that will apply any changes from Stack Configs indicated via the path.
Expand All @@ -36,11 +43,12 @@ def launch_command(ctx: Context, path: str, yes: bool, prune: bool):
* Any stacks that already exist will be updated (if there are any changes)
* If any stacks are marked with "ignore: True", those stacks will neither be created nor updated
* If any stacks are marked with "obsolete: True", those stacks will neither be created nor updated.
Furthermore, if the "-p"/"--prune" flag is used, these stacks will be deleted prior to any
* Furthermore, if the "-p"/"--prune" flag is used, these stacks will be deleted prior to any
other launch commands
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down
4 changes: 4 additions & 0 deletions sceptre/cli/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def list_resources(ctx, path):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down Expand Up @@ -67,6 +68,7 @@ def list_outputs(ctx, path, export):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path", None),
user_variables=ctx.obj.get("user_variables", {}),
options=ctx.obj.get("options", {}),
Expand Down Expand Up @@ -108,6 +110,7 @@ def list_change_sets(ctx, path, url):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
output_format=ctx.obj.get("output_format"),
Expand Down Expand Up @@ -138,6 +141,7 @@ def list_stacks(ctx, path):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
output_format=ctx.obj.get("output_format"),
Expand Down
1 change: 1 addition & 0 deletions sceptre/cli/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def set_policy_command(ctx, path, policy_file, built_in):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down
1 change: 1 addition & 0 deletions sceptre/cli/prune.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def prune_command(ctx, yes: bool, path):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down
1 change: 1 addition & 0 deletions sceptre/cli/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def status_command(ctx, path):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down
4 changes: 4 additions & 0 deletions sceptre/cli/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def validate_command(ctx, no_placeholders, path):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down Expand Up @@ -74,6 +75,7 @@ def generate_command(ctx, no_placeholders, path):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down Expand Up @@ -109,6 +111,7 @@ def estimate_cost_command(ctx, path):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down Expand Up @@ -142,6 +145,7 @@ def fetch_remote_template_command(ctx, path):
"""
context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down
11 changes: 10 additions & 1 deletion sceptre/cli/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import click

from typing import Optional
from sceptre.context import SceptreContext
from sceptre.cli.helpers import catch_exceptions, confirmation
from sceptre.cli.helpers import write, stack_status_exit_code
Expand All @@ -17,9 +18,16 @@
)
@click.option("-v", "--verbose", is_flag=True, help="Display verbose output.")
@click.option("-y", "--yes", is_flag=True, help="Assume yes to all questions.")
@click.option(
"--disable-rollback/--enable-rollback",
default=None,
help="Disable or enable the cloudformation automatic rollback",
)
@click.pass_context
@catch_exceptions
def update_command(ctx, path, change_set, verbose, yes):
def update_command(
ctx, path, change_set, verbose, yes, disable_rollback: Optional[bool]
):
"""
Updates a stack for a given config PATH. Or perform an update via
change-set when the change-set flag is set.
Expand All @@ -37,6 +45,7 @@ def update_command(ctx, path, change_set, verbose, yes):

context = SceptreContext(
command_path=path,
command_params=ctx.params,
project_path=ctx.obj.get("project_path"),
user_variables=ctx.obj.get("user_variables"),
options=ctx.obj.get("options"),
Expand Down
6 changes: 6 additions & 0 deletions sceptre/config/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,11 @@ def _construct_stack(self, rel_path, stack_group_config=None):
)

s3_details = self._collect_s3_details(stack_name, config)
# If disable/enable rollback was specified on the command line, use that. Otherwise,
# fall back to the stack config.
disable_rollback = self.context.command_params.get("disable_rollback")
if disable_rollback is None:
disable_rollback = config.get("disable_rollback", False)

stack = Stack(
name=stack_name,
Expand All @@ -576,6 +581,7 @@ def _construct_stack(self, rel_path, stack_group_config=None):
external_name=config.get("stack_name"),
notifications=config.get("notifications"),
on_failure=config.get("on_failure"),
disable_rollback=disable_rollback,
stack_timeout=config.get("stack_timeout", 0),
ignore=config.get("ignore", False),
obsolete=config.get("obsolete", False),
Expand Down
4 changes: 4 additions & 0 deletions sceptre/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def __init__(
self,
project_path,
command_path,
command_params=None,
user_variables=None,
options=None,
output_format=None,
Expand All @@ -71,6 +72,9 @@ def __init__(

self.normal_command_path = normalise_path(command_path)

# the sceptre command parameters (e.g. sceptre launch <command params>)
self.command_params = command_params or {}

# config_file: stack group config. User definable later in v2
# e.g. {project_path/config/command_path}/config_file
self.config_file = "config.yaml"
Expand Down
6 changes: 5 additions & 1 deletion sceptre/plan/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,12 @@ def create(self):
],
}

if self.stack.on_failure:
# can specify either DisableRollback or OnFailure , but not both
if self.stack.disable_rollback:
create_stack_kwargs.update({"DisableRollback": self.stack.disable_rollback})
elif self.stack.on_failure:
create_stack_kwargs.update({"OnFailure": self.stack.on_failure})

create_stack_kwargs.update(self.stack.template.get_boto_call_parameter())
create_stack_kwargs.update(self._get_role_arn())
create_stack_kwargs.update(self._get_stack_timeout())
Expand Down
Loading

0 comments on commit 99c839a

Please sign in to comment.