Repository: localstack/localstack

Issue:
[PRO] Use construct ID in CDK-deployed lambda function names
<!-- Love localstack? Please consider supporting our collective:
👉  https://opencollective.com/localstack/donate -->

# Type of request: This is a ...

[ ] bug report
[x] feature request

# Detailed description
* Define a lambda function in a CDK stack (e.g. "TestStack"), giving it a descriptive ID value (e.g. "DescriptiveName")
* Deploy the CDK template (`cdklocal deploy`)
* Check the function's name in the dashboard

## Expected behavior
Function's name includes its CDK stack ID. For example, deploying the template to AWS will produce a function name of the form `<StackName>-<FunctionID><mishmash>` (e.g. TestStack-DescriptiveName8C62FF46-hXye7CyPgrc7)

## Actual behavior
Function's name has the form `<StackName>-lambda-<mishmash>` (e.g. TestStack-lambda-cb0e840d), making it more difficult to use in the localstack dashboard when there are several lambdas.

# Steps to reproduce
See detailed description

## Command used to start LocalStack
`LOCALSTACK_API_KEY=... localstack start`

## Client code (AWS SDK code snippet, or sequence of "awslocal" commands)
(none)


Discussion:

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
@@ -1691,7 +1691,11 @@ def _generate_res_name():
         props["LogGroupName"] = resource_name
 
     elif res_type == "AWS::Lambda::Function" and not props.get("FunctionName"):
-        props["FunctionName"] = "{}-lambda-{}".format(stack_name[:45], short_uid())
+        # FunctionName is up to 64 characters long
+        random_id_part = short_uid()
+        resource_id_part = resource_id[:24]
+        stack_name_part = stack_name[: 63 - 2 - (len(random_id_part) + len(resource_id_part))]
+        props["FunctionName"] = f"{stack_name_part}-{resource_id_part}-{random_id_part}"
 
     elif res_type == "AWS::SNS::Topic" and not props.get("TopicName"):
         props["TopicName"] = "topic-%s" % short_uid()


Test patch:
diff --git a/tests/integration/cloudformation/test_cloudformation_lambda.py b/tests/integration/cloudformation/test_cloudformation_lambda.py
new file mode 100644
--- /dev/null
+++ b/tests/integration/cloudformation/test_cloudformation_lambda.py
@@ -0,0 +1,43 @@
+import jinja2
+
+from localstack.utils.common import short_uid
+from localstack.utils.generic.wait_utils import wait_until
+from tests.integration.cloudformation.test_cloudformation_changesets import load_template_raw
+
+
+def test_lambda_autogenerated_name(
+    cfn_client,
+    lambda_client,
+    cleanup_stacks,
+    cleanup_changesets,
+    is_change_set_created_and_available,
+    is_stack_created,
+):
+    stack_name = f"stack-{short_uid()}"
+    change_set_name = f"change-set-{short_uid()}"
+    lambda_functional_id = f"MyFn{short_uid()}"
+    template_rendered = jinja2.Template(load_template_raw("cfn_lambda_noname.yaml")).render(
+        lambda_functional_id=lambda_functional_id
+    )
+
+    response = cfn_client.create_change_set(
+        StackName=stack_name,
+        ChangeSetName=change_set_name,
+        TemplateBody=template_rendered,
+        ChangeSetType="CREATE",
+    )
+    change_set_id = response["Id"]
+    stack_id = response["StackId"]
+
+    try:
+        wait_until(is_change_set_created_and_available(change_set_id))
+        cfn_client.execute_change_set(ChangeSetName=change_set_id)
+        wait_until(is_stack_created(stack_id))
+
+        outputs = cfn_client.describe_stacks(StackName=stack_id)["Stacks"][0]["Outputs"]
+        assert len(outputs) == 1
+        assert lambda_functional_id in outputs[0]["OutputValue"]
+
+    finally:
+        cleanup_changesets([change_set_id])
+        cleanup_stacks([stack_id])
diff --git a/tests/integration/templates/cfn_lambda_noname.yaml b/tests/integration/templates/cfn_lambda_noname.yaml
new file mode 100644
--- /dev/null
+++ b/tests/integration/templates/cfn_lambda_noname.yaml
@@ -0,0 +1,35 @@
+Resources:
+  MyFnServiceRoleF3016589:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Statement:
+          - Action: sts:AssumeRole
+            Effect: Allow
+            Principal:
+              Service: lambda.amazonaws.com
+        Version: "2012-10-17"
+      ManagedPolicyArns:
+        - Fn::Join:
+            - ""
+            - - "arn:"
+              - Ref: AWS::Partition
+              - :iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
+  {{ lambda_functional_id }}:
+    Type: AWS::Lambda::Function
+    Properties:
+      Code:
+        ZipFile: exports.handler = function(event) =>  { console.log(event); };
+      Role:
+        Fn::GetAtt:
+          - MyFnServiceRoleF3016589
+          - Arn
+      Handler: index.handler
+      Runtime: nodejs12.x
+    DependsOn:
+      - MyFnServiceRoleF3016589
+Outputs:
+  FnNameOutput:
+    Description: Name of generated function
+    Value:
+      Ref: {{ lambda_functional_id }}