Skip to content

Commit

Permalink
[Resolves Sceptre#359] Implements use previous paramater feature
Browse files Browse the repository at this point in the history
  • Loading branch information
RobReus committed Jul 1, 2020
1 parent e28e039 commit 78c5160
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 9 deletions.
20 changes: 20 additions & 0 deletions docs/_source/docs/stack_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ values/resolvers. Lists of values/resolvers will be formatted into an AWS
compatible comma separated string e.g. \ ``value1,value2,value3``. Lists can
contain a mixture of values and resolvers.

A parameter can also be configured to use the previous value. You can do so by
making the value a dictionary. The values supported in the dictionary are
``initial_value`` and ``use_previous_value``. When creating stacks, setting
``initial_value`` is required, but can be left out for stack updates. The value
set at ``initial_value`` value will only be used during creation, or when
setting ``use_previous_value`` to false.

Syntax:

.. code-block:: yaml
Expand All @@ -102,6 +109,14 @@ Syntax:
<parameter5_name>:
- !<resolver_name> <resolver_value>
- "value1"
<parameter6_name>:
initial_value: "value"
use_previous_value: <boolean>
<parameter7_name>:
initial_value:
- "value1"
- !<resolver_name> <resolver_value>
use_previous_value: <boolean>
Example:

Expand All @@ -117,6 +132,11 @@ Example:
- "sg-12345678"
- !stack_output security-groups::BaseSecurityGroupId
- !file_contents /file/with/security_group_id.txt
security_group_whitelist:
initial_value:
- "127.0.0.0/24"
- "127.0.1.0/24"
use_previous_value: true
protected
~~~~~~~~~
Expand Down
2 changes: 1 addition & 1 deletion docs/_source/docs/templates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Stack:
Template `dns-extras.j2`:

.. code-block:: yaml
.. code-block:: jinja
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Add Route53 - CNAME and ALIAS records'
Expand Down
7 changes: 7 additions & 0 deletions sceptre/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,10 @@ class InvalidAWSCredentialsError(SceptreException):
Error raised when AWS credentials are invalid.
"""
pass


class InvalidParameterError(SceptreException):
"""
Error raised when parameters are invalid.
"""
pass
30 changes: 23 additions & 7 deletions sceptre/plan/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from sceptre.exceptions import UnknownStackChangeSetStatusError
from sceptre.exceptions import StackDoesNotExistError
from sceptre.exceptions import ProtectedStackError
from sceptre.exceptions import InvalidParameterError


class StackActions(object):
Expand Down Expand Up @@ -59,7 +60,7 @@ def create(self):
self.logger.info("%s - Creating Stack", self.stack.name)
create_stack_kwargs = {
"StackName": self.stack.external_name,
"Parameters": self._format_parameters(self.stack.parameters),
"Parameters": self._format_parameters(self.stack.parameters, create=True),
"Capabilities": ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'],
"NotificationARNs": self.stack.notifications,
"Tags": [
Expand Down Expand Up @@ -640,7 +641,7 @@ def get_status(self):
except StackDoesNotExistError:
return "PENDING"

def _format_parameters(self, parameters):
def _format_parameters(self, parameters, create=False):
"""
Converts CloudFormation parameters to the format used by Boto3.
Expand All @@ -653,12 +654,27 @@ def _format_parameters(self, parameters):
for name, value in parameters.items():
if value is None:
continue
formatted_parameter = dict(ParameterKey=name)
if isinstance(value, list):
value = ",".join(value)
formatted_parameters.append({
"ParameterKey": name,
"ParameterValue": value
})
formatted_parameter['ParameterValue'] = ",".join(value)
elif isinstance(value, dict):
initial_value = value.get('initial_value')
use_previous_value = value.get('use_previous_value', False)
if not isinstance(use_previous_value, bool):
raise InvalidParameterError("'use_previous_value' must be a boolean")
if (create is True or use_previous_value is False) and initial_value is None:
raise InvalidParameterError("'initial_value' is required when creating a new "
"stack or when 'use_previous_value' is false")
if create is True or use_previous_value is False:
if isinstance(initial_value, list):
formatted_parameter['ParameterValue'] = ",".join(initial_value)
else:
formatted_parameter['ParameterValue'] = value.get('initial_value')
else:
formatted_parameter['UsePreviousValue'] = use_previous_value
else:
formatted_parameter['ParameterValue'] = value
formatted_parameters.append(formatted_parameter)

return formatted_parameters

Expand Down
88 changes: 87 additions & 1 deletion tests/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from sceptre.exceptions import UnknownStackChangeSetStatusError
from sceptre.exceptions import StackDoesNotExistError
from sceptre.exceptions import ProtectedStackError
from sceptre.exceptions import InvalidParameterError


class TestStackActions(object):
Expand Down Expand Up @@ -703,7 +704,7 @@ def test_unlock_calls_set_stack_policy_with_policy(
"tests/fixtures/stack_policies/unlock.json"
)

def test_format_parameters_with_sting_values(self):
def test_format_parameters_with_string_values(self):
parameters = {
"key1": "value1",
"key2": "value2",
Expand Down Expand Up @@ -733,6 +734,20 @@ def test_format_parameters_with_none_values(self):
)
assert sorted_formatted_parameters == []

def test_format_parameters_with_empty_dict_value(self):
parameter = {
"key": dict()
}
with pytest.raises(InvalidParameterError):
self.actions._format_parameters(parameter)

def test_format_parameters_with_non_bool_previous_value(self):
parameter = {
"key": dict(use_previous_value='fosho')
}
with pytest.raises(InvalidParameterError):
self.actions._format_parameters(parameter)

def test_format_parameters_with_none_and_string_values(self):
parameters = {
"key1": "value1",
Expand Down Expand Up @@ -815,6 +830,77 @@ def test_format_parameters_with_none_list_and_string_values(self):
{"ParameterKey": "key2", "ParameterValue": "value4"},
]

def test_format_parameters_with_string_and_dict_values(self):
parameters = {
"key1": "value1",
"key2": {"initial_value": "value2"}
}
formatted_parameters = self.actions._format_parameters(parameters)
sorted_formatted_parameters = sorted(
formatted_parameters,
key=lambda x: x["ParameterKey"]
)
assert sorted_formatted_parameters == [
{"ParameterKey": "key1", "ParameterValue": "value1"},
{"ParameterKey": "key2", "ParameterValue": "value2"}
]

def test_format_parameters_with_dict_string_and_list_values(self):
parameters = {
"key1": {"initial_value": ["value1", "value2"]},
"key2": {"initial_value": "value3"}
}
formatted_parameters = self.actions._format_parameters(parameters)
sorted_formatted_parameters = sorted(
formatted_parameters,
key=lambda x: x["ParameterKey"]
)
assert sorted_formatted_parameters == [
{"ParameterKey": "key1", "ParameterValue": "value1,value2"},
{"ParameterKey": "key2", "ParameterValue": "value3"}
]

def test_format_parameters_with_string_and_previous_values(self):
parameters = {
"key1": "value1",
"key2": {"use_previous_value": True},
"key3": {"initial_value": "value3", "use_previous_value": True}
}
formatted_parameters = self.actions._format_parameters(parameters)
sorted_formatted_parameters = sorted(
formatted_parameters,
key=lambda x: x["ParameterKey"]
)
assert sorted_formatted_parameters == [
{"ParameterKey": "key1", "ParameterValue": "value1"},
{"ParameterKey": "key2", "UsePreviousValue": True},
{"ParameterKey": "key3", "UsePreviousValue": True}
]

def test_format_parameters_with_create_and_previous_without_initial_values(self):
parameters = {
"key1": "value1",
"key2": {"use_previous_value": True},
"key3": {"initial_value": "value3", "use_previous_value": True}
}
with pytest.raises(InvalidParameterError):
self.actions._format_parameters(parameters, create=True)

def test_format_parameters_with_create_and_previous_values(self):
parameters = {
"key1": "value1",
"key2": {"initial_value": "value2", "use_previous_value": True}
}
formatted_parameters = self.actions._format_parameters(parameters, create=True)
sorted_formatted_parameters = sorted(
formatted_parameters,
key=lambda x: x["ParameterKey"]
)
assert sorted_formatted_parameters == [
{"ParameterKey": "key1", "ParameterValue": "value1"},
{"ParameterKey": "key2", "ParameterValue": "value2"}
]

@patch("sceptre.plan.actions.StackActions._describe")
def test_get_status_with_created_stack(self, mock_describe):
mock_describe.return_value = {
Expand Down

0 comments on commit 78c5160

Please sign in to comment.