Repository: remind101/stacker
Pull request #202 (2016-08-30 00:15:21)

xref lookup
Depends on this PR: https://github.com/remind101/stacker/pull/201. I was having some issues with the version of `mock` and what was required by `moto`.

This should fix: https://github.com/remind101/stacker/issues/184. It is the same as the `output` Lookup, except we don't use the `Context` to resolve the fully qualified name of the stack.


Patch:
diff --git a/setup.py b/setup.py
index ffe0355..4e700ce 100644
--- a/setup.py
+++ b/setup.py
@@ -7,20 +7,20 @@ VERSION = "0.6.3"
 src_dir = os.path.dirname(__file__)
 
 install_requires = [
-    "troposphere>=1.8.0",
-    "boto3>=1.3.1",
-    "botocore>=1.4.38",
-    "PyYAML>=3.11",
-    "awacs>=0.6.0",
-    "colorama==0.3.7",
+    "troposphere~=1.8.0",
+    "boto3~=1.3.1",
+    "botocore~=1.4.38",
+    "PyYAML~=3.11",
+    "awacs~=0.6.0",
+    "colorama~=0.3.7",
 ]
 
 tests_require = [
-    "nose>=1.0",
-    "mock==1.0.1",
-    "stacker_blueprints",
-    "moto",
-    "testfixtures",
+    "nose~=1.0",
+    "mock~=2.0.0",
+    "stacker_blueprints~=0.6.0",
+    "moto~=0.4.25",
+    "testfixtures~=4.10.0",
 ]
 
 
diff --git a/stacker/lookups/handlers/output.py b/stacker/lookups/handlers/output.py
index 7669c8f..de1776f 100644
--- a/stacker/lookups/handlers/output.py
+++ b/stacker/lookups/handlers/output.py
@@ -5,7 +5,7 @@ TYPE_NAME = "output"
 Output = namedtuple("Output", ("stack_name", "output_name"))
 
 
-def handler(value, provider=None, context=None, **kwargs):
+def handler(value, provider=None, context=None, fqn=False, **kwargs):
     """Fetch an output from the designated stack.
 
     Args:
@@ -14,6 +14,9 @@ def handler(value, provider=None, context=None, **kwargs):
         provider (:class:`stacker.provider.base.BaseProvider`): subclass of the
             base provider
         context (:class:`stacker.context.Context`): stacker context
+        fqn (bool): boolean for whether or not the
+            :class:`stacker.context.Context` should resolve the `fqn` of the
+            stack.
 
     Returns:
         str: output from the specified stack
@@ -25,7 +28,10 @@ def handler(value, provider=None, context=None, **kwargs):
         raise ValueError('Context is required')
 
     d = deconstruct(value)
-    stack_fqn = context.get_fqn(d.stack_name)
+    stack_fqn = d.stack_name
+    if not fqn:
+        stack_fqn = context.get_fqn(d.stack_name)
+
     output = provider.get_output(stack_fqn, d.output_name)
     return output
 
diff --git a/stacker/lookups/handlers/xref.py b/stacker/lookups/handlers/xref.py
new file mode 100644
index 0000000..4b2b1b0
--- /dev/null
+++ b/stacker/lookups/handlers/xref.py
@@ -0,0 +1,21 @@
+"""Handler for fetching outputs from fully qualified stacks.
+
+The `output` handler supports fetching outputs from stacks created within a
+sigle config file. Sometimes it's useful to fetch outputs from stacks created
+outside of the current config file. `xref` supports this by not using the
+:class:`stacker.context.Context` to expand the fqn of the stack.
+
+Example:
+
+    conf_value: ${xref some-fully-qualified-stack-name::SomeOutputName}
+
+"""
+from functools import partial
+
+from .output import handler as output_handler
+
+TYPE_NAME = "xref"
+
+# xref is the same as the `output` handler, except the value already contains
+# the fully qualified name for the stack we're fetching the output from.
+handler = partial(output_handler, fqn=True)
diff --git a/stacker/lookups/registry.py b/stacker/lookups/registry.py
index ba3c421..ba4c894 100644
--- a/stacker/lookups/registry.py
+++ b/stacker/lookups/registry.py
@@ -2,6 +2,7 @@ from ..exceptions import UnknownLookupType
 from ..util import load_object_from_string
 
 from .handlers import output
+from .handlers import xref
 
 LOOKUP_HANDLERS = {}
 DEFAULT_LOOKUP = output.TYPE_NAME
@@ -49,3 +50,4 @@ def resolve_lookups(lookups, context, provider):
     return resolved_lookups
 
 register_lookup_handler(output.TYPE_NAME, output.handler)
+register_lookup_handler(xref.TYPE_NAME, xref.handler)


Test patch:
diff --git a/stacker/tests/lookups/__init__.py b/stacker/tests/lookups/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/stacker/tests/lookups/handlers/__init__.py b/stacker/tests/lookups/handlers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/stacker/tests/lookups/handlers/test_output.py b/stacker/tests/lookups/handlers/test_output.py
new file mode 100644
index 0000000..9b2e1b6
--- /dev/null
+++ b/stacker/tests/lookups/handlers/test_output.py
@@ -0,0 +1,22 @@
+from mock import MagicMock
+import unittest
+
+from stacker.lookups.handlers.output import handler
+
+
+class TestOutputHandler(unittest.TestCase):
+
+    def setUp(self):
+        self.provider = MagicMock()
+        self.context = MagicMock()
+
+    def test_output_handler(self):
+        self.provider.get_output.return_value = "Test Output"
+        self.context.get_fqn.return_value = "fully-qualified-stack-name"
+        value = handler("stack-name::SomeOutput",
+                        provider=self.provider, context=self.context)
+        self.assertEqual(value, "Test Output")
+        self.assertEqual(self.context.get_fqn.call_count, 1)
+        args = self.provider.get_output.call_args
+        self.assertEqual(args[0][0], "fully-qualified-stack-name")
+        self.assertEqual(args[0][1], "SomeOutput")
diff --git a/stacker/tests/lookups/handlers/test_xref.py b/stacker/tests/lookups/handlers/test_xref.py
new file mode 100644
index 0000000..93bd8a7
--- /dev/null
+++ b/stacker/tests/lookups/handlers/test_xref.py
@@ -0,0 +1,21 @@
+from mock import MagicMock
+import unittest
+
+from stacker.lookups.handlers.xref import handler
+
+
+class TestXrefHandler(unittest.TestCase):
+
+    def setUp(self):
+        self.provider = MagicMock()
+        self.context = MagicMock()
+
+    def test_xref_handler(self):
+        self.provider.get_output.return_value = "Test Output"
+        value = handler("fully-qualified-stack-name::SomeOutput",
+                        provider=self.provider, context=self.context)
+        self.assertEqual(value, "Test Output")
+        self.assertEqual(self.context.get_fqn.call_count, 0)
+        args = self.provider.get_output.call_args
+        self.assertEqual(args[0][0], "fully-qualified-stack-name")
+        self.assertEqual(args[0][1], "SomeOutput")
diff --git a/stacker/tests/lookups/test_registry.py b/stacker/tests/lookups/test_registry.py
new file mode 100644
index 0000000..d22d0a3
--- /dev/null
+++ b/stacker/tests/lookups/test_registry.py
@@ -0,0 +1,17 @@
+import unittest
+
+from stacker.lookups.registry import LOOKUP_HANDLERS
+
+
+class TestRegistry(unittest.TestCase):
+
+    def test_autoloaded_lookup_handlers(self):
+        handlers = ["output", "xref"]
+        for handler in handlers:
+            try:
+                LOOKUP_HANDLERS[handler]
+            except KeyError:
+                self.assertTrue(
+                    False,
+                    "Lookup handler: '{}' was not registered".format(handler),
+                )