Repository: remind101/stacker
Pull request #94 (2015-09-10 23:27:45)

Environment file should be a simple key: value pair file.
We no longer want to double yaml parse the environment file. This treats
the environment as a group of dumb key: value pairs, where the raw
string value will be passed to the yaml config.


Patch:
diff --git a/.dockerignore b/.dockerignore
deleted file mode 100644
index 9414382..0000000
--- a/.dockerignore
+++ /dev/null
@@ -1,1 +0,0 @@
-Dockerfile
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e62a105..7806408 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,22 +1,4 @@
-## 0.5.2 (2015-09-10)
-
-- Add Dockerfile/image [GH-87]
-- Clean up environment docs [GH-88]
-- Make StorageType configurable in RDS v2 [GH-92]
-
-## 0.5.1 (2015-09-08)
-
-- Add info subcommand [GH-73]
-- Move namespace into environment [GH-72]
-- Simplified basecommand [GH-74]
-- Documentation updates [GH-75, GH-77, GH-78]
-- aws\_helper removal [GH-79]
-- Move VPC to use LOCAL\_PARAMETERS [GH-81]
-- Lower default AZ count to 2 [GH-82]
-- Allow use of all parameter properties [GH-83]
-- Parameter gathering in method [GH-84]
-- NoEcho on sensitive parameters in blueprnts [GH-85]
-- Version 2 RDS Blueprints [GH-86]
+## pending
 
 ## 0.5.0 (2015-08-13)
 
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 6e7a0d7..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,11 +0,0 @@
-FROM python:2.7.10
-MAINTAINER Mike Barrett
-
-COPY scripts/docker-stacker /bin/docker-stacker
-RUN mkdir -p /stacks
-WORKDIR /stacks
-COPY . /tmp/stacker
-RUN cd /tmp/stacker && python setup.py install && rm -rf /tmp/stacker
-
-ENTRYPOINT ["docker-stacker"]
-CMD ["-h"]
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 670b355..0000000
--- a/Makefile
+++ /dev/null
@@ -1,4 +0,0 @@
-.PHONY: build
-
-build:
-	docker build -t remind101/stacker .
diff --git a/README.rst b/README.rst
index 988d82a..9c6c01d 100644
--- a/README.rst
+++ b/README.rst
@@ -82,6 +82,13 @@ environment. The environment should point to a yaml formatted file that
 contains a flat dictionary (ie: only ``key: value`` pairs).  Those keys
 can be used in the stack config as python `string.Template`_ mappings.
 
+At a minimum the environment must define the ``namespace`` parameter::
+
+  namespace: example.com
+
+This helps preserve the namespace for the stacks between subsequent
+builds.
+
 For example, if you wanted to name a stack based on the environment you were
 building it in, first you would create an environment file with the
 environment name in it (staging in this case)::
@@ -95,24 +102,6 @@ stack), you would have the following::
 
 Stacker would then name the VPC stack ``stageVPC``.
 
-At a minimum the environment must define the ``namespace`` parameter::
-
-  namespace: example.com
-
-The namespace should be a unique name that the stacks will be built under.
-This value will be used to prefix the CloudFormation stack names as well
-as the s3 bucket that contains the stacker templates. Specifying the
-namespace in the environment file helps preserve the namespace for the
-stacks between subsequent builds.
-
 .. _Remind: http://www.remind.com/
 .. _troposphere: https://github.com/cloudtools/troposphere
 .. _string.Template: https://docs.python.org/2/library/string.html#template-strings
-
-Docker
-======
-
-Stack can also be executed from Docker. Use this method to run stacker if you
-want to avoid setting up a python environment::
-
-  docker run -v `pwd`:/stacks remind101/stacker build ...
diff --git a/scripts/docker-stacker b/scripts/docker-stacker
deleted file mode 100755
index bc1f69e..0000000
--- a/scripts/docker-stacker
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-# This script is meant to be used from within the Docker image for stacker. It
-# simply installs the stacks at /stacks and then runs stacker.
-
-set -e
-
-cd /stacks
-python setup.py install
-
-exec stacker $@
diff --git a/setup.py b/setup.py
index 5935e08..746516d 100644
--- a/setup.py
+++ b/setup.py
@@ -26,7 +26,7 @@ def read(filename):
 if __name__ == '__main__':
     setup(
         name='stacker',
-        version='0.5.2',
+        version='0.5.0',
         author='Michael Barrett',
         author_email='loki77@gmail.com',
         license="New BSD license",
diff --git a/stacker/__init__.py b/stacker/__init__.py
index 45869b6..2b8877c 100644
--- a/stacker/__init__.py
+++ b/stacker/__init__.py
@@ -1,1 +1,1 @@
-__version__ = '0.5.2'
+__version__ = '0.5.0'
diff --git a/stacker/blueprints/rds/base.py b/stacker/blueprints/rds/base.py
index 010e14e..4f8f5b1 100644
--- a/stacker/blueprints/rds/base.py
+++ b/stacker/blueprints/rds/base.py
@@ -123,14 +123,6 @@ class BaseRDS(Blueprint):
                 "default": "false",
                 "allowed_values": ["true", "false"]
             },
-            "StorageType": {
-                "type": "String",
-                "description": "Storage type for RDS instance. Defaults to "
-                               "standard unless IOPS is set, then it "
-                               "defaults to io1",
-                "default": "default",
-                "allowed_values": ["default", "standard", "gp2", "io1"]
-            },
             "AllocatedStorage": {
                 "type": "Number",
                 "description": "Space, in GB, to allocate to RDS instance. If "
@@ -233,11 +225,11 @@ class BaseRDS(Blueprint):
                 Condition("HasInternalZoneName"),
                 Condition("HasInternalHostname")))
         t.add_condition(
-            "HasProvisionedIOPS",
+            "ProvisionedIOPS",
             Not(Equals(Ref("IOPS"), "0")))
         t.add_condition(
-            "HasStorageType",
-            Not(Equals(Ref("StorageType"), "default")))
+            "NoProvisionedIOPS",
+            Equals(Ref("IOPS"), "0"))
 
     def create_subnet_group(self):
         t = self.template
@@ -293,13 +285,14 @@ class BaseRDS(Blueprint):
         t.add_resource(
             DBInstance(
                 DBINSTANCE,
-                StorageType=If("HasStorageType",
-                               Ref("StorageType"),
-                               Ref("AWS::NoValue")),
-                Iops=If("HasProvisionedIOPS",
-                        Ref("IOPS"),
-                        Ref("AWS::NoValue")),
-                **self.get_common_attrs()))
+                StorageType=If("NoProvisionedIOPS",
+                               Ref("AWS::NoValue"),
+                               "io1"),
+                Iops=If("NoProvisionedIOPS",
+                        Ref("AWS::NoValue"),
+                        Ref("IOPS")),
+                **self.get_common_attrs()
+                ))
 
     def create_dns_records(self):
         t = self.template
diff --git a/stacker/commands/stacker/base.py b/stacker/commands/stacker/base.py
index 1a386f6..72261d0 100644
--- a/stacker/commands/stacker/base.py
+++ b/stacker/commands/stacker/base.py
@@ -2,7 +2,7 @@ import argparse
 from collections import Mapping
 import logging
 
-import yaml
+from ...environment import parse_environment
 
 DEBUG_FORMAT = ('[%(asctime)s] %(levelname)s %(name)s:%(lineno)d'
                 '(%(funcName)s): %(message)s')
@@ -37,10 +37,10 @@ def key_value_arg(string):
     return {k: v}
 
 
-def yaml_file_type(yaml_file):
-    """ Reads a yaml file and returns the resulting data. """
-    with open(yaml_file) as fd:
-        return yaml.load(fd)
+def environment_file(input_file):
+    """Reads a stacker environment file and returns the resulting data."""
+    with open(input_file) as fd:
+        return parse_environment(fd.read())
 
 
 class BaseCommand(object):
@@ -149,11 +149,12 @@ class BaseCommand(object):
         parser.add_argument("-v", "--verbose", action="count", default=0,
                             help="Increase output verbosity. May be specified "
                                  "up to twice.")
-        parser.add_argument('environment', type=yaml_file_type,
+        parser.add_argument('environment', type=environment_file,
                             default={},
-                            help="Path to a yaml environment file. The values in "
-                                 "the environment file can be used in the stack "
-                                 "config as if it were a string.Template type: "
+                            help="Path to a simple `key: value` pair environment "
+                                 "file. The values in the environment file can "
+                                 "be used in the stack config as if it were a "
+                                 "string.Template type: "
                                  "https://docs.python.org/2/library/string.html"
                                  "#template-strings. Must define at least a "
                                  "'namespace'.")
diff --git a/stacker/environment.py b/stacker/environment.py
new file mode 100644
index 0000000..bce73e9
--- /dev/null
+++ b/stacker/environment.py
@@ -0,0 +1,19 @@
+
+
+def parse_environment(raw_environment):
+    environment = {}
+    for line in raw_environment.split('\n'):
+        line = line.strip()
+        if not line:
+            continue
+
+        if line.startswith('#'):
+            continue
+
+        try:
+            key, value = line.split(':', 1)
+        except ValueError:
+            raise ValueError('Environment must be in key: value format')
+
+        environment[key] = value.strip()
+    return environment


Test patch:
diff --git a/stacker/tests/test_environment.py b/stacker/tests/test_environment.py
new file mode 100644
index 0000000..e207078
--- /dev/null
+++ b/stacker/tests/test_environment.py
@@ -0,0 +1,41 @@
+import unittest
+
+from stacker.environment import parse_environment
+
+test_env = """key1: value1
+# some: comment
+
+ # here: about
+
+# key2
+key2: value2
+
+# another comment here
+key3: some:complex::value
+
+
+# one more here as well
+key4: :otherValue:
+key5: <another>@value
+"""
+
+test_error_env = """key1: valu1
+error
+"""
+
+
+class TestEnvironment(unittest.TestCase):
+
+    def test_simple_key_value_parsing(self):
+        parsed_env = parse_environment(test_env)
+        self.assertTrue(isinstance(parsed_env, dict))
+        self.assertEqual(parsed_env['key1'], 'value1')
+        self.assertEqual(parsed_env['key2'], 'value2')
+        self.assertEqual(parsed_env['key3'], 'some:complex::value')
+        self.assertEqual(parsed_env['key4'], ':otherValue:')
+        self.assertEqual(parsed_env['key5'], '<another>@value')
+        self.assertEqual(len(parsed_env.keys()), 5)
+
+    def test_simple_key_value_parsing_exception(self):
+        with self.assertRaises(ValueError):
+            parse_environment(test_error_env)