-
Notifications
You must be signed in to change notification settings - Fork 311
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Add a `detect-stack-drift` command and tests. The `detect-stack-drift` command calls the `detect_stack_drift` Boto3 call, takes the Detector Id returned, then waits for `describe_stack_drift_detection_status(StackDriftDetectionId=detector_id)` to complete, and then finally returns `describe_stack_resource_drifts` as a JSON document. If `--debug` is passed, sceptre also will provide feedback on the detection progress.
- Loading branch information
1 parent
d6ecbd2
commit 520b0fe
Showing
13 changed files
with
651 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
Feature: Drift Detection | ||
Scenario: Detects no drift on a stack with no drift | ||
Given stack "drift-single/A" exists using "topic.yaml" | ||
When the user detects drift on stack "drift-single/A" | ||
Then stack drift status is "IN_SYNC" | ||
|
||
Scenario: Shows no drift on a stack that with no drift | ||
Given stack "drift-single/A" exists using "topic.yaml" | ||
When the user shows drift on stack "drift-single/A" | ||
Then stack resource drift status is "IN_SYNC" | ||
|
||
Scenario: Detects drift on a stack that has drifted | ||
Given stack "drift-single/A" exists using "topic.yaml" | ||
And a topic configuration in stack "drift-single/A" has drifted | ||
When the user detects drift on stack "drift-single/A" | ||
Then stack drift status is "DRIFTED" | ||
|
||
Scenario: Shows drift on a stack that has drifted | ||
Given stack "drift-single/A" exists using "topic.yaml" | ||
And a topic configuration in stack "drift-single/A" has drifted | ||
When the user shows drift on stack "drift-single/A" | ||
Then stack resource drift status is "MODIFIED" | ||
|
||
Scenario: Detects drift on a stack group that partially exists | ||
Given stack "drift-group/A" exists using "topic.yaml" | ||
And stack "drift-group/B" does not exist | ||
And a topic configuration in stack "drift-group/A" has drifted | ||
When the user detects drift on stack_group "drift-group" | ||
Then stack_group drift statuses are each one of "DRIFTED,STACK_DOES_NOT_EXIST" | ||
|
||
Scenario: Does not blow up on a stack group that doesn't exist | ||
Given stack_group "drift-group" does not exist | ||
When the user detects drift on stack_group "drift-group" | ||
Then stack_group drift statuses are each one of "STACK_DOES_NOT_EXIST,STACK_DOES_NOT_EXIST" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
template: | ||
path: loggroup.yaml | ||
type: file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
template: | ||
path: loggroup.yaml | ||
type: file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
template: | ||
path: loggroup.yaml | ||
type: file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
Resources: | ||
Topic: | ||
Type: AWS::SNS::Topic | ||
Properties: | ||
DisplayName: MyTopic | ||
|
||
Outputs: | ||
TopicName: | ||
Value: !Ref Topic | ||
Export: | ||
Name: !Sub "${AWS::StackName}-TopicName" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
from behave import * | ||
from helpers import get_cloudformation_stack_name | ||
|
||
import boto3 | ||
|
||
from sceptre.plan.plan import SceptrePlan | ||
from sceptre.context import SceptreContext | ||
|
||
|
||
@given('a topic configuration in stack "{stack_name}" has drifted') | ||
def step_impl(context, stack_name): | ||
full_name = get_cloudformation_stack_name(context, stack_name) | ||
topic_arn = _get_output("TopicName", full_name) | ||
client = boto3.client("sns") | ||
client.set_topic_attributes( | ||
TopicArn=topic_arn, | ||
AttributeName="DisplayName", | ||
AttributeValue="WrongName" | ||
) | ||
|
||
|
||
def _get_output(output_name, stack_name): | ||
client = boto3.client("cloudformation") | ||
response = client.describe_stacks(StackName=stack_name) | ||
for output in response["Stacks"][0]["Outputs"]: | ||
if output["OutputKey"] == output_name: | ||
return output["OutputValue"] | ||
|
||
|
||
@when('the user detects drift on stack "{stack_name}"') | ||
def step_impl(context, stack_name): | ||
sceptre_context = SceptreContext( | ||
command_path=stack_name + '.yaml', | ||
project_path=context.sceptre_dir | ||
) | ||
sceptre_plan = SceptrePlan(sceptre_context) | ||
values = sceptre_plan.drift_detect().values() | ||
context.output = list(values) | ||
|
||
|
||
@when('the user shows drift on stack "{stack_name}"') | ||
def step_impl(context, stack_name): | ||
sceptre_context = SceptreContext( | ||
command_path=stack_name + '.yaml', | ||
project_path=context.sceptre_dir | ||
) | ||
sceptre_plan = SceptrePlan(sceptre_context) | ||
values = sceptre_plan.drift_show().values() | ||
context.output = list(values) | ||
|
||
|
||
@when('the user detects drift on stack_group "{stack_group_name}"') | ||
def step_impl(context, stack_group_name): | ||
sceptre_context = SceptreContext( | ||
command_path=stack_group_name, | ||
project_path=context.sceptre_dir | ||
) | ||
sceptre_plan = SceptrePlan(sceptre_context) | ||
values = sceptre_plan.drift_detect().values() | ||
context.output = list(values) | ||
|
||
|
||
@then('stack drift status is "{desired_status}"') | ||
def step_impl(context, desired_status): | ||
assert context.output[0]["StackDriftStatus"] == desired_status | ||
|
||
|
||
@then('stack resource drift status is "{desired_status}"') | ||
def step_impl(context, desired_status): | ||
assert context.output[0][1]["StackResourceDrifts"][0]["StackResourceDriftStatus"] == desired_status | ||
|
||
|
||
@then('stack_group drift statuses are each one of "{statuses}"') | ||
def step_impl(context, statuses): | ||
status_list = [status.strip() for status in statuses.split(",")] | ||
for output in context.output: | ||
assert output["StackDriftStatus"] in status_list |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import click | ||
from click import Context | ||
|
||
from sceptre.context import SceptreContext | ||
from sceptre.plan.plan import SceptrePlan | ||
|
||
from sceptre.cli.helpers import ( | ||
catch_exceptions, | ||
deserialize_json_properties, | ||
write | ||
) | ||
|
||
BAD_STATUSES = [ | ||
"DETECTION_FAILED", | ||
"TIMED_OUT" | ||
] | ||
|
||
|
||
@click.group(name="drift") | ||
def drift_group(): | ||
""" | ||
Commands for calling drift detection. | ||
""" | ||
pass | ||
|
||
|
||
@drift_group.command(name="detect", short_help="Run detect stack drift on running stacks.") | ||
@click.argument("path") | ||
@click.pass_context | ||
@catch_exceptions | ||
def drift_detect(ctx: Context, path: str): | ||
""" | ||
Detect stack drift and return stack drift status. | ||
In the event that the stack does not exist, we return | ||
a DetectionStatus and StackDriftStatus of STACK_DOES_NOT_EXIST. | ||
In the event that drift detection times out, we return | ||
a DetectionStatus and StackDriftStatus of TIMED_OUT. | ||
The timeout is set at 5 minutes, a value that cannot be configured. | ||
""" | ||
context = SceptreContext( | ||
command_path=path, | ||
project_path=ctx.obj.get("project_path"), | ||
user_variables=ctx.obj.get("user_variables"), | ||
options=ctx.obj.get("options"), | ||
output_format=ctx.obj.get("output_format"), | ||
ignore_dependencies=ctx.obj.get("ignore_dependencies") | ||
) | ||
|
||
plan = SceptrePlan(context) | ||
responses = plan.drift_detect() | ||
|
||
output_format = "json" if context.output_format == "json" else "yaml" | ||
|
||
exit_status = 0 | ||
for stack, response in responses.items(): | ||
status = response["DetectionStatus"] | ||
if status in BAD_STATUSES: | ||
exit_status += 1 | ||
for key in ["Timestamp", "ResponseMetadata"]: | ||
response.pop(key, None) | ||
write({stack.external_name: deserialize_json_properties(response)}, output_format) | ||
|
||
exit(exit_status) | ||
|
||
|
||
@drift_group.command(name="show", short_help="Shows stack drift on running stacks.") | ||
@click.argument("path") | ||
@click.pass_context | ||
@catch_exceptions | ||
def drift_show(ctx, path): | ||
""" | ||
Show stack drift on deployed stacks. | ||
In the event that the stack does not exist, we return | ||
a StackResourceDriftStatus of STACK_DOES_NOT_EXIST. | ||
In the event that drift detection times out, we return | ||
a StackResourceDriftStatus of TIMED_OUT. | ||
The timeout is set at 5 minutes, a value that cannot be configured. | ||
""" | ||
context = SceptreContext( | ||
command_path=path, | ||
project_path=ctx.obj.get("project_path"), | ||
user_variables=ctx.obj.get("user_variables"), | ||
options=ctx.obj.get("options"), | ||
output_format=ctx.obj.get("output_format"), | ||
ignore_dependencies=ctx.obj.get("ignore_dependencies") | ||
) | ||
|
||
plan = SceptrePlan(context) | ||
responses = plan.drift_show() | ||
|
||
output_format = "json" if context.output_format == "json" else "yaml" | ||
|
||
exit_status = 0 | ||
for stack, (status, response) in responses.items(): | ||
if status in BAD_STATUSES: | ||
exit_status += 1 | ||
response.pop("ResponseMetadata", None) | ||
write({stack.external_name: deserialize_json_properties(response)}, output_format) | ||
|
||
exit(exit_status) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.