From 5868c9d4e476cc65c26e8b09e72ce0a1de6ed250 Mon Sep 17 00:00:00 2001 From: Kyle Finley Date: Sat, 9 Mar 2019 18:25:57 -0800 Subject: [PATCH 1/9] move handle_hook to avoid circle import, add lookup resolve to handle_hooks --- stacker/hooks/utils.py | 83 ++++++++++++++++++++++++++++++++++++++++++ stacker/util.py | 69 ----------------------------------- 2 files changed, 83 insertions(+), 69 deletions(-) diff --git a/stacker/hooks/utils.py b/stacker/hooks/utils.py index 350d7a6b4..c5f816175 100644 --- a/stacker/hooks/utils.py +++ b/stacker/hooks/utils.py @@ -2,7 +2,90 @@ from __future__ import division from __future__ import absolute_import import os +import sys +import collections +import logging + +from ..variables import Variable, resolve_variables +from ..util import load_object_from_string + +logger = logging.getLogger(__name__) def full_path(path): return os.path.abspath(os.path.expanduser(path)) + + +def handle_hooks(stage, hooks, provider, context): + """ Used to handle pre/post_build hooks. + + These are pieces of code that we want to run before/after the builder + builds the stacks. + + Args: + stage (string): The current stage (pre_run, post_run, etc). + hooks (list): A list of :class:`stacker.config.Hook` containing the + hooks to execute. + provider (:class:`stacker.provider.base.BaseProvider`): The provider + the current stack is using. + context (:class:`stacker.context.Context`): The current stacker + context. + """ + if not hooks: + logger.debug("No %s hooks defined.", stage) + return + + hook_paths = [] + for i, h in enumerate(hooks): + try: + hook_paths.append(h.path) + except KeyError: + raise ValueError("%s hook #%d missing path." % (stage, i)) + + logger.info("Executing %s hooks: %s", stage, ", ".join(hook_paths)) + for hook in hooks: + data_key = hook.data_key + required = hook.required + enabled = hook.enabled + + if isinstance(hook.args, dict): + args = [Variable(k, v) for k, v in hook.args.items()] + resolve_variables(args, context, provider) + kwargs = {v.name: v.value for v in args} + else: + kwargs = hook.args or {} + + if not enabled: + logger.debug("hook with method %s is disabled, skipping", + hook.path) + continue + try: + method = load_object_from_string(hook.path) + except (AttributeError, ImportError): + logger.exception("Unable to load method at %s:", hook.path) + if required: + raise + continue + try: + result = method(context=context, provider=provider, **kwargs) + except Exception: + logger.exception("Method %s threw an exception:", hook.path) + if required: + raise + continue + if not result: + if required: + logger.error("Required hook %s failed. Return value: %s", + hook.path, result) + sys.exit(1) + logger.warning("Non-required hook %s failed. Return value: %s", + hook.path, result) + else: + if isinstance(result, collections.Mapping): + if data_key: + logger.debug("Adding result for hook %s to context in " + "data_key %s.", hook.path, data_key) + context.set_hook_data(data_key, result) + else: + logger.debug("Hook %s returned result data, but no data " + "key set, so ignoring.", hook.path) diff --git a/stacker/util.py b/stacker/util.py index 4f95a52f6..dc9e1394d 100644 --- a/stacker/util.py +++ b/stacker/util.py @@ -16,7 +16,6 @@ import tempfile import zipfile -import collections from collections import OrderedDict import botocore.client @@ -337,74 +336,6 @@ def cf_safe_name(name): return "".join([uppercase_first_letter(part) for part in parts]) -def handle_hooks(stage, hooks, provider, context): - """ Used to handle pre/post_build hooks. - - These are pieces of code that we want to run before/after the builder - builds the stacks. - - Args: - stage (string): The current stage (pre_run, post_run, etc). - hooks (list): A list of :class:`stacker.config.Hook` containing the - hooks to execute. - provider (:class:`stacker.provider.base.BaseProvider`): The provider - the current stack is using. - context (:class:`stacker.context.Context`): The current stacker - context. - """ - if not hooks: - logger.debug("No %s hooks defined.", stage) - return - - hook_paths = [] - for i, h in enumerate(hooks): - try: - hook_paths.append(h.path) - except KeyError: - raise ValueError("%s hook #%d missing path." % (stage, i)) - - logger.info("Executing %s hooks: %s", stage, ", ".join(hook_paths)) - for hook in hooks: - data_key = hook.data_key - required = hook.required - kwargs = hook.args or {} - enabled = hook.enabled - if not enabled: - logger.debug("hook with method %s is disabled, skipping", - hook.path) - continue - try: - method = load_object_from_string(hook.path) - except (AttributeError, ImportError): - logger.exception("Unable to load method at %s:", hook.path) - if required: - raise - continue - try: - result = method(context=context, provider=provider, **kwargs) - except Exception: - logger.exception("Method %s threw an exception:", hook.path) - if required: - raise - continue - if not result: - if required: - logger.error("Required hook %s failed. Return value: %s", - hook.path, result) - sys.exit(1) - logger.warning("Non-required hook %s failed. Return value: %s", - hook.path, result) - else: - if isinstance(result, collections.Mapping): - if data_key: - logger.debug("Adding result for hook %s to context in " - "data_key %s.", hook.path, data_key) - context.set_hook_data(data_key, result) - else: - logger.debug("Hook %s returned result data, but no data " - "key set, so ignoring.", hook.path) - - def get_config_directory(): """Return the directory the config file is located in. From 64b69feb2822ea0c0515821aec10f85f5c28f100 Mon Sep 17 00:00:00 2001 From: Kyle Finley Date: Sat, 9 Mar 2019 18:26:32 -0800 Subject: [PATCH 2/9] update handle_hooks import in actions --- stacker/actions/build.py | 4 ++-- stacker/actions/destroy.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/stacker/actions/build.py b/stacker/actions/build.py index bd2b91714..03732cbc3 100644 --- a/stacker/actions/build.py +++ b/stacker/actions/build.py @@ -7,7 +7,7 @@ from .base import STACK_POLL_TIME from ..providers.base import Template -from .. import util +from ..hooks import utils from ..exceptions import ( MissingParameterException, StackDidNotChange, @@ -196,7 +196,7 @@ def handle_hooks(stage, hooks, provider, context, dump, outline): """ if not outline and not dump and hooks: - util.handle_hooks( + utils.handle_hooks( stage=stage, hooks=hooks, provider=provider, diff --git a/stacker/actions/destroy.py b/stacker/actions/destroy.py index 4f26692ad..d76a4284b 100644 --- a/stacker/actions/destroy.py +++ b/stacker/actions/destroy.py @@ -6,7 +6,7 @@ from .base import BaseAction, plan, build_walker from .base import STACK_POLL_TIME from ..exceptions import StackDoesNotExist -from .. import util +from ..hooks import utils from ..status import ( CompleteStatus, SubmittedStatus, @@ -82,7 +82,7 @@ def pre_run(self, outline=False, *args, **kwargs): """Any steps that need to be taken prior to running the action.""" pre_destroy = self.context.config.pre_destroy if not outline and pre_destroy: - util.handle_hooks( + utils.handle_hooks( stage="pre_destroy", hooks=pre_destroy, provider=self.provider, @@ -106,7 +106,7 @@ def post_run(self, outline=False, *args, **kwargs): """Any steps that need to be taken after running the action.""" post_destroy = self.context.config.post_destroy if not outline and post_destroy: - util.handle_hooks( + utils.handle_hooks( stage="post_destroy", hooks=post_destroy, provider=self.provider, From f747c84cb2b264398a8bff59a1df581126c1b124 Mon Sep 17 00:00:00 2001 From: Kyle Finley Date: Sat, 9 Mar 2019 18:27:40 -0800 Subject: [PATCH 3/9] move handle_hooks tests to appropriate file, update import in tests --- stacker/tests/hooks/test_utils.py | 158 ++++++++++++++++++++++++++++++ stacker/tests/test_context.py | 2 +- stacker/tests/test_util.py | 150 +--------------------------- 3 files changed, 160 insertions(+), 150 deletions(-) create mode 100644 stacker/tests/hooks/test_utils.py diff --git a/stacker/tests/hooks/test_utils.py b/stacker/tests/hooks/test_utils.py new file mode 100644 index 000000000..e26690103 --- /dev/null +++ b/stacker/tests/hooks/test_utils.py @@ -0,0 +1,158 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +from future import standard_library +standard_library.install_aliases() + +import unittest + +import queue + +from stacker.config import Hook +from stacker.hooks.utils import handle_hooks + +from ..factories import ( + mock_context, + mock_provider, +) + +hook_queue = queue.Queue() + + +def mock_hook(*args, **kwargs): + hook_queue.put(kwargs) + return True + + +def fail_hook(*args, **kwargs): + return None + + +def exception_hook(*args, **kwargs): + raise Exception + + +def context_hook(*args, **kwargs): + return "context" in kwargs + + +def result_hook(*args, **kwargs): + return {"foo": "bar"} + + +class TestHooks(unittest.TestCase): + + def setUp(self): + self.context = mock_context(namespace="namespace") + self.provider = mock_provider(region="us-east-1") + + def test_empty_hook_stage(self): + hooks = [] + handle_hooks("fake", hooks, self.provider, self.context) + self.assertTrue(hook_queue.empty()) + + def test_missing_required_hook(self): + hooks = [Hook({"path": "not.a.real.path", "required": True})] + with self.assertRaises(ImportError): + handle_hooks("missing", hooks, self.provider, self.context) + + def test_missing_required_hook_method(self): + hooks = [{"path": "stacker.hooks.blah", "required": True}] + with self.assertRaises(AttributeError): + handle_hooks("missing", hooks, self.provider, self.context) + + def test_missing_non_required_hook_method(self): + hooks = [Hook({"path": "stacker.hooks.blah", "required": False})] + handle_hooks("missing", hooks, self.provider, self.context) + self.assertTrue(hook_queue.empty()) + + def test_default_required_hook(self): + hooks = [Hook({"path": "stacker.hooks.blah"})] + with self.assertRaises(AttributeError): + handle_hooks("missing", hooks, self.provider, self.context) + + def test_valid_hook(self): + hooks = [ + Hook({"path": "stacker.tests.hooks.test_utils.mock_hook", + "required": True})] + handle_hooks("missing", hooks, self.provider, self.context) + good = hook_queue.get_nowait() + self.assertEqual(good["provider"].region, "us-east-1") + with self.assertRaises(queue.Empty): + hook_queue.get_nowait() + + def test_valid_enabled_hook(self): + hooks = [ + Hook({"path": "stacker.tests.hooks.test_utils.mock_hook", + "required": True, "enabled": True})] + handle_hooks("missing", hooks, self.provider, self.context) + good = hook_queue.get_nowait() + self.assertEqual(good["provider"].region, "us-east-1") + with self.assertRaises(queue.Empty): + hook_queue.get_nowait() + + def test_valid_enabled_false_hook(self): + hooks = [ + Hook({"path": "stacker.tests.hooks.test_utils.mock_hook", + "required": True, "enabled": False})] + handle_hooks("missing", hooks, self.provider, self.context) + self.assertTrue(hook_queue.empty()) + + def test_context_provided_to_hook(self): + hooks = [ + Hook({"path": "stacker.tests.hooks.test_utils.context_hook", + "required": True})] + handle_hooks("missing", hooks, "us-east-1", self.context) + + def test_hook_failure(self): + hooks = [ + Hook({"path": "stacker.tests.hooks.test_utils.fail_hook", + "required": True})] + with self.assertRaises(SystemExit): + handle_hooks("fail", hooks, self.provider, self.context) + hooks = [{"path": "stacker.tests.hooks.test_utils.exception_hook", + "required": True}] + with self.assertRaises(Exception): + handle_hooks("fail", hooks, self.provider, self.context) + hooks = [ + Hook({"path": "stacker.tests.hooks.test_utils.exception_hook", + "required": False})] + # Should pass + handle_hooks("ignore_exception", hooks, self.provider, self.context) + + def test_return_data_hook(self): + hooks = [ + Hook({ + "path": "stacker.tests.hooks.test_utils.result_hook", + "data_key": "my_hook_results" + }), + # Shouldn't return data + Hook({ + "path": "stacker.tests.hooks.test_utils.context_hook" + }) + ] + handle_hooks("result", hooks, "us-east-1", self.context) + + self.assertEqual( + self.context.hook_data["my_hook_results"]["foo"], + "bar" + ) + # Verify only the first hook resulted in stored data + self.assertEqual( + list(self.context.hook_data.keys()), ["my_hook_results"] + ) + + def test_return_data_hook_duplicate_key(self): + hooks = [ + Hook({ + "path": "stacker.tests.hooks.test_utils.result_hook", + "data_key": "my_hook_results" + }), + Hook({ + "path": "stacker.tests.hooks.test_utils.result_hook", + "data_key": "my_hook_results" + }) + ] + + with self.assertRaises(KeyError): + handle_hooks("result", hooks, "us-east-1", self.context) diff --git a/stacker/tests/test_context.py b/stacker/tests/test_context.py index 088fed5f0..0015aab39 100644 --- a/stacker/tests/test_context.py +++ b/stacker/tests/test_context.py @@ -5,7 +5,7 @@ from stacker.context import Context, get_fqn from stacker.config import load, Config -from stacker.util import handle_hooks +from stacker.hooks.utils import handle_hooks class TestContext(unittest.TestCase): diff --git a/stacker/tests/test_util.py b/stacker/tests/test_util.py index 9c4fa7635..e936ec79e 100644 --- a/stacker/tests/test_util.py +++ b/stacker/tests/test_util.py @@ -8,18 +8,16 @@ import string import os -import queue import mock import boto3 -from stacker.config import Hook, GitPackageSource +from stacker.config import GitPackageSource from stacker.util import ( cf_safe_name, load_object_from_string, camel_to_snake, - handle_hooks, merge_map, yaml_to_ordered_dict, get_client_region, @@ -33,10 +31,6 @@ SourceProcessor ) -from .factories import ( - mock_context, - mock_provider, -) regions = ["us-east-1", "cn-north-1", "ap-northeast-1", "eu-west-1", "ap-southeast-1", "ap-southeast-2", "us-west-2", "us-gov-west-1", @@ -274,148 +268,6 @@ def test_SourceProcessor_helpers(self): ) -hook_queue = queue.Queue() - - -def mock_hook(*args, **kwargs): - hook_queue.put(kwargs) - return True - - -def fail_hook(*args, **kwargs): - return None - - -def exception_hook(*args, **kwargs): - raise Exception - - -def context_hook(*args, **kwargs): - return "context" in kwargs - - -def result_hook(*args, **kwargs): - return {"foo": "bar"} - - -class TestHooks(unittest.TestCase): - - def setUp(self): - self.context = mock_context(namespace="namespace") - self.provider = mock_provider(region="us-east-1") - - def test_empty_hook_stage(self): - hooks = [] - handle_hooks("fake", hooks, self.provider, self.context) - self.assertTrue(hook_queue.empty()) - - def test_missing_required_hook(self): - hooks = [Hook({"path": "not.a.real.path", "required": True})] - with self.assertRaises(ImportError): - handle_hooks("missing", hooks, self.provider, self.context) - - def test_missing_required_hook_method(self): - hooks = [{"path": "stacker.hooks.blah", "required": True}] - with self.assertRaises(AttributeError): - handle_hooks("missing", hooks, self.provider, self.context) - - def test_missing_non_required_hook_method(self): - hooks = [Hook({"path": "stacker.hooks.blah", "required": False})] - handle_hooks("missing", hooks, self.provider, self.context) - self.assertTrue(hook_queue.empty()) - - def test_default_required_hook(self): - hooks = [Hook({"path": "stacker.hooks.blah"})] - with self.assertRaises(AttributeError): - handle_hooks("missing", hooks, self.provider, self.context) - - def test_valid_hook(self): - hooks = [ - Hook({"path": "stacker.tests.test_util.mock_hook", - "required": True})] - handle_hooks("missing", hooks, self.provider, self.context) - good = hook_queue.get_nowait() - self.assertEqual(good["provider"].region, "us-east-1") - with self.assertRaises(queue.Empty): - hook_queue.get_nowait() - - def test_valid_enabled_hook(self): - hooks = [ - Hook({"path": "stacker.tests.test_util.mock_hook", - "required": True, "enabled": True})] - handle_hooks("missing", hooks, self.provider, self.context) - good = hook_queue.get_nowait() - self.assertEqual(good["provider"].region, "us-east-1") - with self.assertRaises(queue.Empty): - hook_queue.get_nowait() - - def test_valid_enabled_false_hook(self): - hooks = [ - Hook({"path": "stacker.tests.test_util.mock_hook", - "required": True, "enabled": False})] - handle_hooks("missing", hooks, self.provider, self.context) - self.assertTrue(hook_queue.empty()) - - def test_context_provided_to_hook(self): - hooks = [ - Hook({"path": "stacker.tests.test_util.context_hook", - "required": True})] - handle_hooks("missing", hooks, "us-east-1", self.context) - - def test_hook_failure(self): - hooks = [ - Hook({"path": "stacker.tests.test_util.fail_hook", - "required": True})] - with self.assertRaises(SystemExit): - handle_hooks("fail", hooks, self.provider, self.context) - hooks = [{"path": "stacker.tests.test_util.exception_hook", - "required": True}] - with self.assertRaises(Exception): - handle_hooks("fail", hooks, self.provider, self.context) - hooks = [ - Hook({"path": "stacker.tests.test_util.exception_hook", - "required": False})] - # Should pass - handle_hooks("ignore_exception", hooks, self.provider, self.context) - - def test_return_data_hook(self): - hooks = [ - Hook({ - "path": "stacker.tests.test_util.result_hook", - "data_key": "my_hook_results" - }), - # Shouldn't return data - Hook({ - "path": "stacker.tests.test_util.context_hook" - }) - ] - handle_hooks("result", hooks, "us-east-1", self.context) - - self.assertEqual( - self.context.hook_data["my_hook_results"]["foo"], - "bar" - ) - # Verify only the first hook resulted in stored data - self.assertEqual( - list(self.context.hook_data.keys()), ["my_hook_results"] - ) - - def test_return_data_hook_duplicate_key(self): - hooks = [ - Hook({ - "path": "stacker.tests.test_util.result_hook", - "data_key": "my_hook_results" - }), - Hook({ - "path": "stacker.tests.test_util.result_hook", - "data_key": "my_hook_results" - }) - ] - - with self.assertRaises(KeyError): - handle_hooks("result", hooks, "us-east-1", self.context) - - class TestException1(Exception): pass From 2d0f88e9da92ffcd4f410c39fa9257296a9632e0 Mon Sep 17 00:00:00 2001 From: Kyle Finley Date: Sat, 9 Mar 2019 18:58:09 -0800 Subject: [PATCH 4/9] add test for resolved lookup in hook_data --- stacker/tests/hooks/test_utils.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/stacker/tests/hooks/test_utils.py b/stacker/tests/hooks/test_utils.py index e26690103..05f71a11a 100644 --- a/stacker/tests/hooks/test_utils.py +++ b/stacker/tests/hooks/test_utils.py @@ -40,6 +40,10 @@ def result_hook(*args, **kwargs): return {"foo": "bar"} +def kwargs_hook(*args, **kwargs): + return kwargs + + class TestHooks(unittest.TestCase): def setUp(self): @@ -156,3 +160,20 @@ def test_return_data_hook_duplicate_key(self): with self.assertRaises(KeyError): handle_hooks("result", hooks, "us-east-1", self.context) + + def test_resolve_lookups_in_args(self): + hooks = [ + Hook({ + "path": "stacker.tests.hooks.test_utils.kwargs_hook", + "data_key": "my_hook_results", + "args": { + "default_lookup": "${default env_var::default_value}" + } + }) + ] + handle_hooks("lookups", hooks, "us-east-1", self.context) + + self.assertEqual( + self.context.hook_data["my_hook_results"]["default_lookup"], + "default_value" + ) From 89eeda43443a8c8bd254f6fdd518e743a3bdce2e Mon Sep 17 00:00:00 2001 From: Kyle Finley Date: Sat, 9 Mar 2019 20:07:33 -0700 Subject: [PATCH 5/9] updated CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e48fc3199..09d137143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Upcoming release +- add the ability to resolve native lookups in hook args + ## 1.6.0 (2019-01-21) - New lookup format/syntax, making it more generic [GH-665] From 943acbfa0cf5eaf51e607acda332eee87cadae58 Mon Sep 17 00:00:00 2001 From: Michael Barrett Date: Mon, 8 Apr 2019 15:09:29 -0700 Subject: [PATCH 6/9] Fix lint issues --- stacker/tests/test_util.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/stacker/tests/test_util.py b/stacker/tests/test_util.py index ad138afa3..843f795e9 100644 --- a/stacker/tests/test_util.py +++ b/stacker/tests/test_util.py @@ -31,13 +31,6 @@ SourceProcessor ) -from stacker.hooks.utils import handle_hooks - -from .factories import ( - mock_context, - mock_provider, -) - regions = ["us-east-1", "cn-north-1", "ap-northeast-1", "eu-west-1", "ap-southeast-1", "ap-southeast-2", "us-west-2", "us-gov-west-1", "us-west-1", "eu-central-1", "sa-east-1"] From cdeea85222c2622d6854f4ea402d553ba672ff11 Mon Sep 17 00:00:00 2001 From: Kyle Finley Date: Tue, 9 Apr 2019 09:48:24 -0700 Subject: [PATCH 7/9] add error detail for pre_build hooks using output lookup or similar --- stacker/hooks/utils.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/stacker/hooks/utils.py b/stacker/hooks/utils.py index 1db76c998..3b6687c26 100644 --- a/stacker/hooks/utils.py +++ b/stacker/hooks/utils.py @@ -6,6 +6,7 @@ import collections import logging +from ..exceptions import FailedVariableLookup from ..variables import Variable, resolve_variables from stacker.util import load_object_from_string @@ -51,7 +52,19 @@ def handle_hooks(stage, hooks, provider, context): if isinstance(hook.args, dict): args = [Variable(k, v) for k, v in hook.args.items()] - resolve_variables(args, context, provider) + try: # handling for output or similar being used in pre_build + resolve_variables(args, context, provider) + except FailedVariableLookup as err: + # pylint: disable=no-member + if stage == 'pre_build' and \ + "NoneType" in err.message: # excludes detailed errors + logger.error("Lookups that change the order of " + "execution, like 'output', can only be " + "used in 'post_build' hooks. Please " + "ensure that the hook being used does " + "not rely on a stack, hook_data, or " + "context that does not exist yet.") + raise err kwargs = {v.name: v.value for v in args} else: kwargs = hook.args or {} From a3d023e627b1b6cac527f2628aff2dcdd00af351 Mon Sep 17 00:00:00 2001 From: Kyle Finley Date: Tue, 9 Apr 2019 10:40:22 -0700 Subject: [PATCH 8/9] add lookup usage to config/hooks documentation --- docs/config.rst | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index a6804926b..f2c97d02b 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -234,7 +234,9 @@ The keyword is a list of dictionaries with the following keys: that grants you the ability to execute a hook per environment when combined with a variable pulled from an environment file. **args:** - a dictionary of arguments to pass to the hook + a dictionary of arguments to pass to the hook with support for lookups. + Note that lookups that change the order of execution, like ``output``, can + only be used in a `post` hook. An example using the *create_domain* hook for creating a route53 domain before the build action:: @@ -258,6 +260,30 @@ should run in the environment stacker is running against:: args: domain: mydomain.com +An example of a custom hooks using various lookups in it's arguments, +shown using a dictionary to define the hooks:: + + pre_build: + custom_hook1: + path: path.to.hook1.entry_point + args: + ami: ${ami [@]owners:self,888888888888,amazon name_regex:server[0-9]+ architecture:i386} + user_data: ${file parameterized-64:file://some/path} + db_endpoint: ${rxref some-stack::Endpoint} + db_creds: ${ssmstore us-east-1@MyDBUser} + custom_hook2: + path: path.to.hook.entry_point + args: + bucket_name: ${dynamodb us-east-1:TestTable@TestKey:TestVal.BucketName} + files: + - ${file plain:file://some/path} + + post_build: + custom_hook3: + path: path.to.hook3.entry_point + args: + nlb: ${output nlb-stack::Nlb} + Tags ---- From 742e89f66952d9e26b3ab90ce829a204978158c3 Mon Sep 17 00:00:00 2001 From: Kyle Finley Date: Tue, 9 Apr 2019 10:49:17 -0700 Subject: [PATCH 9/9] updated condition and log message to include all pre/post hooks --- stacker/hooks/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stacker/hooks/utils.py b/stacker/hooks/utils.py index 3b6687c26..ab7fcb638 100644 --- a/stacker/hooks/utils.py +++ b/stacker/hooks/utils.py @@ -56,11 +56,11 @@ def handle_hooks(stage, hooks, provider, context): resolve_variables(args, context, provider) except FailedVariableLookup as err: # pylint: disable=no-member - if stage == 'pre_build' and \ + if 'pre' in stage and \ "NoneType" in err.message: # excludes detailed errors logger.error("Lookups that change the order of " "execution, like 'output', can only be " - "used in 'post_build' hooks. Please " + "used in 'post_*' hooks. Please " "ensure that the hook being used does " "not rely on a stack, hook_data, or " "context that does not exist yet.")