8000 Use more condensed output by mwildehahn · Pull Request #182 · cloudtools/stacker · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Use more condensed output #182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions stacker/commands/stacker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ class Stacker(BaseCommand):

def configure(self, options, **kwargs):
super(Stacker, self).configure(options, **kwargs)
imode = getattr(options, 'interactive', False)
if imode:
if options.interactive:
logger.info('Using Interactive AWS Provider')
options.provider = interactive.Provider(region=options.region)
options.provider = interactive.Provider(
region=options.region,
replacements_only=options.replacements_only,
)
else:
logger.info('Using Default AWS Provider')
options.provider = default.Provider(region=options.region)
Expand Down
8 changes: 6 additions & 2 deletions stacker/commands/stacker/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,14 @@ def add_arguments(self, parser):
parser.add_argument("config", type=argparse.FileType(),
help="The config file where stack configuration "
"is located. Must be in yaml format.")
parser.add_argument("-i", "--interactive", action='store_true',
parser.add_argument("-i", "--interactive", action="store_true",
help="Enable interactive mode. If specified, this "
"will use the AWS interactive provider, which "
"leverages Cloudformation Change Sets to display "
"changes before running cloudformation templates. "
"You'll be asked if you want to execute each change "
"set.")
8000 "set. If you only want to authorize replacements, "
"run with \"--replacements-only\" as well.")
parser.add_argument("--replacements-only", action="store_true",
help="If interactive mode is enabled, stacker will "
"only prompt to authorize replacements.")
103 changes: 95 additions & 8 deletions stacker/providers/aws/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,92 @@ def get_change_set_name():
return 'change-set-{}'.format(int(time.time()))


def requires_replacement(changeset):
"""Return the changes within the changeset that require replacement.

Args:
changeset (list): List of changes

Returns:
list: A list of changes that require replacement, if any.

"""
return [r for r in changeset if r["ResourceChange"]["Replacement"] ==
'True']


def ask_for_approval(full_changeset=None, include_verbose=False):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring

"""Prompt the user for approval to execute a change set.

Args:
full_changeset (Optional[list]): A list of the full changeset that will
be output if the user specifies verbose.
include_verbose (Optional[bool]): Boolean for whether or not to include
the verbose option

"""
approval_options = ['y', 'n']
if include_verbose:
approval_options.append('v')

approve = raw_input("Execute the above changes? [{}] ".format(
'/'.join(approval_options)))

if include_verbose and approve == "v":
logger.info(
"Full changeset:\n%s",
yaml.safe_dump(full_changeset),
)
return ask_for_approval()
elif approve != "y":
raise exceptions.CancelExecution


def output_summary(fqn, action, changeset, replacements_only=False):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring

"""Log a summary of the changeset.

Args:
fqn (string): fully qualified name of the stack
action (string): action to include in the log message
changeset (list): AWS changeset
replacements_only (Optional[bool]): boolean for whether or not we only
want to list replacements

"""
replacements = []
changes = []
for change in changeset:
resource = change['ResourceChange']
replacement = resource['Replacement'] == 'True'
summary = '- %s %s (%s)' % (
resource['Action'],
resource['LogicalResourceId'],
resource['ResourceType'],
)
if replacement:
replacements.append(summary)
else:
changes.append(summary)

summary = ''
if replacements:
if not replacements_only:
summary += 'Replacements:\n'
summary += '\n'.join(replacements)
if changes:
if summary:
summary += '\n'
summary += 'Changes:\n%s' % ('\n'.join(changes))
logger.info('%s %s:\n%s', fqn, action, summary)


class Provider(AWSProvider):
"""AWS Cloudformation Change Set Provider"""

def __init__(self, *args, **kwargs):
self.replacements_only = kwargs.pop('replacements_only', False)
super(Provider, self).__init__(*args, **kwargs)

def _wait_till_change_set_complete(self, change_set_id):
complete = False
response = None
Expand Down Expand Up @@ -71,14 +154,18 @@ def update_stack(self, fqn, template_url, parameters, tags, **kwargs):
if response["ExecutionStatus"] != "AVAILABLE":
raise Exception("Unable to execute change set: {}".format(response))

message = (
"Cloudformation wants to make the following changes to stack: "
"%s\n%s"
)
logger.info(message, fqn, yaml.safe_dump(response["Changes"]))
approve = raw_input("Execute the above changes? [y/n] ")
if approve != "y":
raise exceptions.CancelExecution
action = "replacements" if self.replacements_only else "changes"
changeset = response["Changes"]
if self.replacements_only:
changeset = requires_replacement(changeset)

if len(changeset):
output_summary(fqn, action, changeset,
replacements_only=self.replacements_only)
ask_for_approval(
full_changeset=response["Changes"],
include_verbose=True,
)

retry_on_throttling(
self.cloudformation.execute_change_set,
Expand Down
Empty file.
Empty file.
32 changes: 32 additions & 0 deletions stacker/tests/providers/aws/test_interactive.py
48E3
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import unittest
from ....providers.aws.interactive import requires_replacement


def generate_resource_change(replacement=True):
resource_change = {
"Action": "Modify",
"Details": [],
"LogicalResourceId": "Fake",
"PhysicalResourceId": "arn:aws:fake",
"Replacement": "True" if replacement else "False",
"ResourceType": "AWS::Fake",
"Scope": ["Properties"],
}
return {
"ResourceChange": resource_change,
"Type": "Resource",
}


class TestInteractiveProvider(unittest.TestCase):

def test_requires_replacement(self):
changeset = [
generate_resource_change(),
generate_resource_change(replacement=False),
generate_resource_change(),
]
replacement = requires_replacement(changeset)
self.assertEqual(len(replacement), 2)
for resource in replacement:
self.assertEqual(resource["ResourceChange"]["Replacement"], "True")
0