Skip to content

Commit 8c185d8

Browse files
authored
add hook args lookup resolution with a test and doc updates (#143)
implementation from cloudtools/stacker#708
1 parent 8276de6 commit 8c185d8

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed

docs/source/cfngin/config.rst

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
.. _graph: ../terminology.html#graph
77
.. _hook: ../terminology.html#hook
88
.. _hooks: ../terminology.html#hook
9+
.. _lookups: lookups.html
910
.. _Mappings: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html
1011
.. _Outputs: ../terminology.html#output
1112
.. _stack: ../terminology.html#stack
@@ -353,7 +354,10 @@ The keyword is a dictionary with the following keys:
353354
with a variable pulled from an environment file.
354355

355356
**args:**
356-
A dictionary of arguments to pass to the hook_.
357+
A dictionary of arguments to pass to the hook_ with support for lookups_.
358+
Note that lookups_ that change the order of execution, like ``output``, can
359+
only be used in a `post` hook but hooks like ``rxref`` are able to be used
360+
with either `pre` or `post` hooks.
357361

358362
An example using the ``create_domain`` hook_ for creating a route53 domain before
359363
the build action:
@@ -383,6 +387,33 @@ should run in the environment CFNgin is running against:
383387
args:
384388
domain: mydomain.com
385389
390+
An example of a custom hooks using various lookups in it's arguments:
391+
392+
.. code-block:: yaml
393+
394+
pre_build:
395+
custom_hook1:
396+
path: path.to.hook1.entry_point
397+
args:
398+
ami: ${ami [<region>@]owners:self,888888888888,amazon name_regex:server[0-9]+ architecture:i386}
399+
user_data: ${file parameterized-64:file://some/path}
400+
db_endpoint: ${rxref some-stack::Endpoint}
401+
subnet: ${xref some-stack::Subnet}
402+
db_creds: ${ssmstore us-east-1@MyDBUser}
403+
custom_hook2:
404+
path: path.to.hook.entry_point
405+
args:
406+
bucket: ${dynamodb us-east-1:TestTable@TestKey:TestVal.BucketName}
407+
bucket_region: ${envvar AWS_REGION} # this variable is set by Runway
408+
files:
409+
- ${file plain:file://some/path}
410+
411+
post_build:
412+
custom_hook3:
413+
path: path.to.hook3.entry_point
414+
args:
415+
nlb: ${output nlb-stack::Nlb} # output can only be used as a post hook
416+
386417
387418
Tags
388419
----

runway/cfngin/hooks/utils.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import sys
66

77
from runway.util import load_object_from_string
8+
from runway.variables import Variable, resolve_variables
9+
10+
from ..exceptions import FailedVariableLookup
811

912
LOGGER = logging.getLogger(__name__)
1013

@@ -43,26 +46,45 @@ def handle_hooks(stage, hooks, provider, context):
4346
for hook in hooks:
4447
data_key = hook.data_key
4548
required = hook.required
46-
kwargs = hook.args or {}
47-
enabled = hook.enabled
48-
if not enabled:
49+
50+
if not hook.enabled:
4951
LOGGER.debug("hook with method %s is disabled, skipping",
5052
hook.path)
5153
continue
54+
5255
try:
5356
method = load_object_from_string(hook.path)
5457
except (AttributeError, ImportError):
5558
LOGGER.exception("Unable to load method at %s:", hook.path)
5659
if required:
5760
raise
5861
continue
62+
63+
if isinstance(hook.args, dict):
64+
args = [Variable(k, v) for k, v in hook.args.items()]
65+
try: # handling for output or similar being used in pre_build
66+
resolve_variables(args, context, provider)
67+
except FailedVariableLookup:
68+
if 'pre' in stage:
69+
LOGGER.error('Lookups that change the order of '
70+
'execution, like "output", can only be '
71+
'used in "post_*" hooks. Please '
72+
'ensure that the hook being used does '
73+
'not rely on a stack, hook_data, or '
74+
'context that does not exist yet.')
75+
raise
76+
kwargs = {v.name: v.value for v in args}
77+
else:
78+
kwargs = hook.args or {}
79+
5980
try:
6081
result = method(context=context, provider=provider, **kwargs)
6182
except Exception: # pylint: disable=broad-except
6283
LOGGER.exception("Method %s threw an exception:", hook.path)
6384
if required:
6485
raise
6586
continue
87+
6688
if not result:
6789
if required:
6890
LOGGER.error("Required hook %s failed. Return value: %s",

tests/cfngin/hooks/test_utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,22 @@ def test_return_data_hook_duplicate_key(self):
142142
with self.assertRaises(KeyError):
143143
handle_hooks("result", hooks, "us-east-1", self.context)
144144

145+
def test_resolve_lookups_in_args(self):
146+
"""Test the resolution of lookups in hook args."""
147+
hooks = [Hook({
148+
"path": "tests.cfngin.hooks.test_utils.kwargs_hook",
149+
"data_key": "my_hook_results",
150+
"args": {
151+
"default_lookup": "${default env_var::default_value}"
152+
}
153+
})]
154+
handle_hooks("lookups", hooks, "us-east-1", self.context)
155+
156+
self.assertEqual(
157+
self.context.hook_data["my_hook_results"]["default_lookup"],
158+
"default_value"
159+
)
160+
145161

146162
def mock_hook(*args, **kwargs):
147163
"""Mock hook."""
@@ -167,3 +183,8 @@ def context_hook(*args, **kwargs):
167183
def result_hook(*args, **kwargs):
168184
"""Results hook."""
169185
return {"foo": "bar"}
186+
187+
188+
def kwargs_hook(*args, **kwargs):
189+
"""Kwargs hook."""
190+
return kwargs

0 commit comments

Comments
 (0)