Repository: localstack/localstack

Issue:
Cloudformation Nested Stacks should allow parameters from parent
I spent a couple of hours yesterday trying to deploy a parent stack with two nested dynamodb table stacks. Both stacks were to be built from the same template and so provided a TableName parameter for the general purpose child stack to use along with its standard settings. This is one of the primary use cases for child stacks.

It didn’t work, and looking at #1921 it appears that the code treats the child stack as though it should just run independently.

I suspect that #2079 needs the obverse of this, which is the collection of outputs from a child to use with a peer.



Discussion:
Here's some extra info.

Parent file:

```yml
AWSTemplateFormatVersion: 2010-09-09
Description: SomeParent

Parameters:
  GlobalAppStageName:
    Description: Global - App Stage Name
    Type: String
    Default: 'dev'

Resources:
  EngineTable:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./dynamo-component.yml
      Parameters:
        GlobalAppStageName: !Ref GlobalAppStageName
        TypeName: 'foo'
  FleetTable:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./dynamo-component.yml
      Parameters:
        GlobalAppStageName: !Ref GlobalAppStageName
        TypeName: 'bar'
```

**dynamo-component.yml**

```yml
AWSTemplateFormatVersion: 2010-09-09

Parameters:
  GlobalAppStageName:
    Description: Global - App Stage Name
    Type: String
    Default: 'local'
  TypeName:
    Description: Name of table
    Type: String

Resources:
  EngineTable:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        -
          AttributeName: "id"
          AttributeType: "S"
      BillingMode: PROVISIONED
      KeySchema:
        -
          AttributeName: "id"
          KeyType: "HASH"
      ProvisionedThroughput:
        ReadCapacityUnits: "5"
        WriteCapacityUnits: "5"
      TableName: !Sub 'my-foo-prefix-${GlobalAppStageName}-${TypeName}'
```

As with #1921 the child stack can be made individually, but rolling it into a parent stack doesn't work. Note, how both the stage and the table name are parameters that need to be passed down to the child here.

Resolving patch:
diff --git a/localstack/utils/cloudformation/template_deployer.py b/localstack/utils/cloudformation/template_deployer.py
--- a/localstack/utils/cloudformation/template_deployer.py
+++ b/localstack/utils/cloudformation/template_deployer.py
@@ -73,9 +73,17 @@ def do_replace(params, **kwargs):
     return do_replace
 
 
-def get_nested_stack_name(params, **kwargs):
+def get_nested_stack_params(params, **kwargs):
     stack_name = kwargs.get('stack_name', 'stack')
-    return '%s-%s' % (stack_name, common.short_uid())
+    nested_stack_name = '%s-%s' % (stack_name, common.short_uid())
+    stack_params = params.get('Parameters', {})
+    stack_params = [{'ParameterKey': k, 'ParameterValue': v} for k, v in stack_params.items()]
+    result = {
+        'StackName': nested_stack_name,
+        'TemplateURL': params.get('TemplateURL'),
+        'Parameters': stack_params
+    }
+    return result
 
 
 def get_lambda_code_param(params, **kwargs):
@@ -434,10 +442,7 @@ def replace(params, **kwargs):
     'CloudFormation::Stack': {
         'create': {
             'function': 'create_stack',
-            'parameters': {
-                'StackName': get_nested_stack_name,
-                'TemplateURL': 'TemplateURL'
-            }
+            'parameters': get_nested_stack_params
         }
     }
 }


Test patch:
diff --git a/tests/integration/test_cloudformation.py b/tests/integration/test_cloudformation.py
--- a/tests/integration/test_cloudformation.py
+++ b/tests/integration/test_cloudformation.py
@@ -46,21 +46,31 @@ def handler(event, context):
 """
 TEST_TEMPLATE_5 = """
 AWSTemplateFormatVersion: 2010-09-09
+Parameters:
+  LocalParam:
+    Description: Local stack parameter (passed from parent stack)
+    Type: String
 Resources:
   S3Setup:
     Type: AWS::S3::Bucket
     Properties:
-      BucketName: test-%s
-""" % short_uid()
+      BucketName: !Sub 'test-${LocalParam}'
+"""
 TEST_ARTIFACTS_BUCKET = 'cf-artifacts'
 TEST_ARTIFACTS_PATH = 'stack.yaml'
 TEST_TEMPLATE_6 = """
 AWSTemplateFormatVersion: 2010-09-09
+Parameters:
+  GlobalParam:
+    Description: Global stack parameter
+    Type: String
 Resources:
   NestedStack:
     Type: AWS::CloudFormation::Stack
     Properties:
       TemplateURL: http://localhost:4572/%s/%s
+      Parameters:
+        LocalParam: !Ref GlobalParam
 """ % (TEST_ARTIFACTS_BUCKET, TEST_ARTIFACTS_PATH)
 
 TEST_TEMPLATE_7 = json.dumps({
@@ -354,11 +364,16 @@ def test_nested_stack(self):
         # deploy template
         buckets_before = len(s3.list_buckets()['Buckets'])
         stack_name = 'stack-%s' % short_uid()
-        cloudformation.create_stack(StackName=stack_name, TemplateBody=TEST_TEMPLATE_6)
+        param_value = short_uid()
+        cloudformation.create_stack(StackName=stack_name, TemplateBody=TEST_TEMPLATE_6,
+            Parameters=[{'ParameterKey': 'GlobalParam', 'ParameterValue': param_value}])
 
         # assert that nested resources have been created
-        buckets_after = len(s3.list_buckets()['Buckets'])
-        self.assertEqual(buckets_after, buckets_before + 1)
+        buckets_after = s3.list_buckets()['Buckets']
+        num_buckets_after = len(buckets_after)
+        self.assertEqual(num_buckets_after, buckets_before + 1)
+        bucket_names = [b['Name'] for b in buckets_after]
+        self.assertIn('test-%s' % param_value, bucket_names)
 
         # delete the stack
         cloudformation.delete_stack(StackName=stack_name)