Repository: remind101/stacker
Pull request #61 (2015-08-06 23:27:40)

Local parameters for python
This allows the definition of local parameters that are meant to be used
by python, not by cloudformation.  The parameters are still passed into
the blueprint the same way (from the command line, or the config) but
they can be used without having to share them with the template.


Patch:
diff --git a/stacker/blueprints/base.py b/stacker/blueprints/base.py
index 3636d63..7a02ac3 100644
--- a/stacker/blueprints/base.py
+++ b/stacker/blueprints/base.py
@@ -3,9 +3,49 @@ import logging
 
 from troposphere import Parameter, Template
 
+from ..exceptions import MissingLocalParameterException
+
 logger = logging.getLogger(__name__)
 
 
+def get_local_parameters(parameter_def, parameters):
+    """Gets local parameters from parameter list.
+
+    Given a local parameter definition, and a list of parameters, extract the
+    local parameters, or use a default if provided. If the parameter isn't
+    present, and there is no default, then throw an exception.
+
+    Args:
+        parameter_def (dict): A dictionary of expected/allowed parameters
+            and their defaults. If a parameter is in the list, but does not
+            have a default, it is considered required.
+        parameters (dict): A dictionary of parameters to pull local parameters
+            from.
+
+    Returns:
+        dict: A dictionary of local parameters.
+
+    Raises:
+        MissingLocalParameterException: If a parameter is defined in
+            parameter_def, does not have a default, and does not exist in
+            parameters.
+
+    """
+    local = {}
+
+    for param, attrs in parameter_def.items():
+        try:
+            value = parameters[param]
+        except KeyError:
+            try:
+                value = attrs['default']
+            except KeyError:
+                raise MissingLocalParameterException(param)
+        local[param] = value
+
+    return local
+
+
 class Blueprint(object):
     """Base implementation for dealing with a troposphere template.
 
@@ -23,6 +63,7 @@ class Blueprint(object):
         self.mappings = mappings
         self.context = context
         self.outputs = {}
+        self.local_parameters = self.get_local_parameters()
         self.reset_template()
 
     @property
@@ -38,12 +79,21 @@ class Blueprint(object):
                 required.append((k, v))
         return required
 
+    def get_local_parameters(self):
+        local_parameters = getattr(self, 'LOCAL_PARAMETERS', {})
+        return get_local_parameters(local_parameters, self.context.parameters)
+
     def setup_parameters(self):
         t = self.template
-        parameters = getattr(self, 'PARAMETERS')
+        # First look for CF_PARAMETERS, then fall back to regular PARAMETERS
+        # for backwards compatibility.
+        parameters = getattr(self, 'CF_PARAMETERS',
+                             getattr(self, 'PARAMETERS', {}))
+
         if not parameters:
             logger.debug("No parameters defined.")
             return
+
         for param, attrs in parameters.items():
             p = Parameter(param,
                           Type=attrs.get('type'),
diff --git a/stacker/exceptions.py b/stacker/exceptions.py
index 24184d0..cefb5de 100644
--- a/stacker/exceptions.py
+++ b/stacker/exceptions.py
@@ -11,13 +11,22 @@ class MissingParameterException(Exception):
 
     def __init__(self, parameters, *args, **kwargs):
         self.parameters = parameters
-        message = 'Missing required parameters: %s' % (
+        message = 'Missing required cloudformation parameters: %s' % (
             ', '.join(parameters),
         )
         super(MissingParameterException, self).__init__(message, *args,
                                                         **kwargs)
 
 
+class MissingLocalParameterException(Exception):
+
+    def __init__(self, parameter, *args, **kwargs):
+        self.parameter = parameter
+        message = 'Missing required local parameter: %s' % parameter
+        super(MissingLocalParameterException, self).__init__(message, *args,
+                                                             **kwargs)
+
+
 class OutputDoesNotExist(Exception):
 
     def __init__(self, stack_name, output, *args, **kwargs):


Test patch:
diff --git a/stacker/tests/blueprints/__init__.py b/stacker/tests/blueprints/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/stacker/tests/blueprints/test_base.py b/stacker/tests/blueprints/test_base.py
new file mode 100644
index 0000000..be4ea15
--- /dev/null
+++ b/stacker/tests/blueprints/test_base.py
@@ -0,0 +1,29 @@
+import unittest
+
+from stacker.blueprints.base import get_local_parameters
+from stacker.exceptions import MissingLocalParameterException
+
+
+class TestLocalParameters(unittest.TestCase):
+    def test_default_parameter(self):
+        parameter_def = {'Param1': {'default': 0}}
+        parameters = {}
+
+        local = get_local_parameters(parameter_def, parameters)
+        self.assertEquals(local['Param1'], 0)
+
+    def test_missing_required(self):
+        parameter_def = {'Param1': {'default': 0}, 'Param2': {}}
+        parameters = {}
+
+        with self.assertRaises(MissingLocalParameterException) as cm:
+            get_local_parameters(parameter_def, parameters)
+
+        self.assertEquals('Param2', cm.exception.parameter)
+
+    def test_supplied_parameter(self):
+        parameter_def = {'Param1': {'default': 0}, 'Param2': {}}
+        parameters = {'Param1': 1, 'Param2': 2}
+
+        local = get_local_parameters(parameter_def, parameters)
+        self.assertEquals(parameters, local)