Repository: localstack/localstack

Issue:
bug: Redeploying lambda with CDK fails "localstack.services.awslambda.lambda_api.ClientError: Uploaded Lambda code for runtime (nodejs14.x) is not in Zip format"
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

When using CDK to deploy a simple Lambda function the stack deploys first time and is successful. 
Though when deployed a second time it fails. 

If I change the name of the lambda it will be successfully. 

### Expected Behavior

To redeploy and update the lambda successfully 

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)

    `docker-compose up` 

Compose file: 

```yaml
version: '3.8'

services:
  localstack:
    container_name: '${LOCALSTACK_DOCKER_NAME-localstack_main}'
    image: localstack/localstack
    network_mode: bridge
    ports:
      - '127.0.0.1:53:53' # only required for Pro
      - '127.0.0.1:53:53/udp' # only required for Pro
      - '127.0.0.1:443:443' # only required for Pro
      - '127.0.0.1:4510-4530:4510-4530' # only required for Pro
      - '127.0.0.1:4566:4566'
      - '127.0.0.1:4571:4571'
    environment:
      - SERVICES=${SERVICES- }
      - DEBUG=${DEBUG- }
      - DATA_DIR=${DATA_DIR- }
      - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR- }
      - LOCALSTACK_API_KEY=${LOCALSTACK_API_KEY- } # only required for Pro
      - HOST_TMP_FOLDER=${TMPDIR:-/tmp/}localstack
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:
      - '${TMPDIR:-/tmp}/localstack:/tmp/localstack'
      - '/var/run/docker.sock:/var/run/docker.sock'
```

Stack file:

```ts
import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
import { Construct } from '@aws-cdk/core';
import { Environment } from '../environment/environment';

function getStackName(environment: Environment): string {
  if (environment === 'local') {
    return 'local';
  }

  return `project-${environment}`;
}

export class TestServiceStack extends cdk.Stack {
  constructor(scope: cdk.App, environment: Environment, props?: cdk.StackProps) {
    super(scope, getStackName(environment), props);

    const lambda = this.createS3PackageReceived(this);
  }

  private createS3PackageReceived(owner: Construct): lambda.Function {
    const handler = new lambda.Function(owner, 'fn', {
      code: lambda.Code.fromInline(`exports.handler = async (event, context) => {return "hello"}`),
      handler: 'index.handler',
      functionName: 'test-func3',
      runtime: lambda.Runtime.NODEJS_14_X,
    });

    return handler;
  }
}
```

#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

Deployments are done with: 
```bash
docker-compose up
npx cdklocal bootstrap -v
npx cdklocal deploy --progress events --require-approval never # will be successful 
npx cdklocal deploy --progress events --require-approval never # will fail 
```

In the logs there are two interesting things: 
1.  `2021-10-25T22:35:51:WARNING:localstack.utils.cloudformation.template_deployer: Unable to deploy resource type "CDK::Metadata": {'Type': 'AWS::CDK::Metadata', 'Metadata': {'aws:cdk:path': 'local/CDKMetadata/Default'}, 'Condition': 'CDKMetadataAvailable', 'LogicalResourceId': 'CDKMetadata', 'Properties': {'Analytics': 'v2:deflate64:H4sIAAAAAAAAE02MSQoCMRBFz9L7dLXlgLgTGjxAPEFMIlQPVZBBFyF31yiCq/fgDwgFcQeb7myesbduHqwED+WajJ3VKBxTyDap8c7aR8nB+ubvwFEi4apwe/qfN5bFrDdnoFwy29Zqk59XRWaFomX5PDXWWhWL8zDF4YF7wCMcuikS9SFzotWD/vIFs9XuYq4AAAA='}}` 
  This warning is generated for both successful and unsuccessful deployments

2. 
```
2021-10-25T22:36:50:ERROR:lambda_api: Exception on /2015-03-31/functions/test-func/code [PUT]
Traceback (most recent call last):
  File "/opt/code/localstack/.venv/lib/python3.7/site-packages/flask/app.py", line 2073, in wsgi_app
    response = self.full_dispatch_request()
  File "/opt/code/localstack/.venv/lib/python3.7/site-packages/flask/app.py", line 1518, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/opt/code/localstack/.venv/lib/python3.7/site-packages/flask_cors/extension.py", line 165, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "/opt/code/localstack/.venv/lib/python3.7/site-packages/flask/app.py", line 1516, in full_dispatch_request
    rv = self.dispatch_request()
  File "/opt/code/localstack/.venv/lib/python3.7/site-packages/flask/app.py", line 1502, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/opt/code/localstack/localstack/services/awslambda/lambda_api.py", line 1482, in update_function_code
    result = set_function_code(lambda_function)
  File "/opt/code/localstack/localstack/services/awslambda/lambda_api.py", line 1013, in set_function_code
    run_for_max_seconds(config.LAMBDA_CODE_EXTRACT_TIME, _set_and_configure)
  File "/opt/code/localstack/localstack/utils/common.py", line 1747, in run_for_max_seconds
    raise result
  File "/opt/code/localstack/localstack/utils/common.py", line 1728, in _worker
    fn_result = _function(*args, **kwargs)
  File "/opt/code/localstack/localstack/services/awslambda/lambda_api.py", line 1006, in _set_and_configure
    lambda_handler = do_set_function_code(lambda_function)
  File "/opt/code/localstack/localstack/services/awslambda/lambda_api.py", line 1081, in do_set_function_code
    "Uploaded Lambda code for runtime ({}) is not in Zip format".format(runtime)
localstack.services.awslambda.lambda_api.ClientError: Uploaded Lambda code for runtime (nodejs14.x) is not in Zip format
``` 
   this is generated on the second deployment. This stack trace is generated multiple times. 



### Environment

```markdown
- OS: OSX 11.6
- LocalStack: 
  - localstack_main | LocalStack version: 0.12.19.1
  - localstack_main | LocalStack Docker container id: deb3b6cc94e5
  - localstack_main | LocalStack build date: 2021-10-25
  - localstack_main | LocalStack build git hash: 109e0173
- package.json dependencies 

{
  "devDependencies": {
    "@aws-cdk/assert": "1.113.0",
    "@types/express": "^4.17.13",
    "@types/jest": "^26.0.23",
    "@types/supertest": "^2.0.11",
    "aws-cdk": "^1.129.0",
    "aws-cdk-local": "^1.65.8",
    "jest": "^26.6.3",
    "nodemon": "^2.0.10",
    "supertest": "^6.1.3",
    "ts-jest": "^26.5.5",
    "ts-node": "^10.0.0",
    "typescript": "^4.3.3"
  },
  "dependencies": {
    "@aws-cdk/aws-lambda": "^1.113.0",
    "@aws-cdk/aws-lambda-event-sources": "^1.129.0",
    "@aws-cdk/aws-s3": "^1.113.0",
    "@aws-cdk/core": "1.113.0",
    "@squiz-dx/asset-api": "^1.0.0",
    "express": "^4.17.1"
  }
}
```
```


### Anything else?

_No response_


Discussion:

Resolving patch:
diff --git a/localstack/services/cloudformation/models/awslambda.py b/localstack/services/cloudformation/models/awslambda.py
--- a/localstack/services/cloudformation/models/awslambda.py
+++ b/localstack/services/cloudformation/models/awslambda.py
@@ -9,7 +9,17 @@
 )
 from localstack.services.cloudformation.service_models import LOG, REF_ID_ATTRS, GenericBaseModel
 from localstack.utils.aws import aws_stack
-from localstack.utils.common import cp_r, is_base64, mkdir, new_tmp_dir, rm_rf, save_file, short_uid
+from localstack.utils.common import (
+    cp_r,
+    is_base64,
+    is_zip_file,
+    mkdir,
+    new_tmp_dir,
+    rm_rf,
+    save_file,
+    short_uid,
+    to_bytes,
+)
 from localstack.utils.testutil import create_zip_file
 
 
@@ -52,6 +62,7 @@ def update_resource(self, new_resource, stack_name, resources):
                     'Updating code for Lambda "%s" from location: %s'
                     % (props["FunctionName"], code)
                 )
+            code = LambdaFunction.get_lambda_code_param(props)
             client.update_function_code(FunctionName=props["FunctionName"], **code)
         if "Environment" in update_props:
             environment_variables = update_props["Environment"].get("Variables", {})
@@ -69,34 +80,33 @@ def add_defaults(resource, stack_name: str):
             )
 
     @staticmethod
-    def get_deploy_templates():
-        def get_lambda_code_param(params, **kwargs):
-            code = params.get("Code", {})
-            zip_file = code.get("ZipFile")
-            if zip_file and not is_base64(zip_file):
-                tmp_dir = new_tmp_dir()
-                handler_file = get_handler_file_from_name(
-                    params["Handler"], runtime=params["Runtime"]
-                )
-                tmp_file = os.path.join(tmp_dir, handler_file)
-                save_file(tmp_file, zip_file)
-
-                # add 'cfn-response' module to archive - see:
-                # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html
-                cfn_response_tmp_file = get_cfn_response_mod_file()
-                cfn_response_mod_dir = os.path.join(tmp_dir, "node_modules", "cfn-response")
-                mkdir(cfn_response_mod_dir)
-                cp_r(
-                    cfn_response_tmp_file,
-                    os.path.join(cfn_response_mod_dir, "index.js"),
-                )
+    def get_lambda_code_param(params, **kwargs):
+        code = params.get("Code", {})
+        zip_file = code.get("ZipFile")
+        if zip_file and not is_base64(zip_file) and not is_zip_file(to_bytes(zip_file)):
+            tmp_dir = new_tmp_dir()
+            handler_file = get_handler_file_from_name(params["Handler"], runtime=params["Runtime"])
+            tmp_file = os.path.join(tmp_dir, handler_file)
+            save_file(tmp_file, zip_file)
+
+            # add 'cfn-response' module to archive - see:
+            # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html
+            cfn_response_tmp_file = get_cfn_response_mod_file()
+            cfn_response_mod_dir = os.path.join(tmp_dir, "node_modules", "cfn-response")
+            mkdir(cfn_response_mod_dir)
+            cp_r(
+                cfn_response_tmp_file,
+                os.path.join(cfn_response_mod_dir, "index.js"),
+            )
 
-                # create zip file
-                zip_file = create_zip_file(tmp_dir, get_content=True)
-                code["ZipFile"] = zip_file
-                rm_rf(tmp_dir)
-            return code
+            # create zip file
+            zip_file = create_zip_file(tmp_dir, get_content=True)
+            code["ZipFile"] = zip_file
+            rm_rf(tmp_dir)
+        return code
 
+    @staticmethod
+    def get_deploy_templates():
         def get_delete_params(params, **kwargs):
             return {"FunctionName": params.get("FunctionName")}
 
@@ -115,7 +125,7 @@ def get_environment_params(params, **kwargs):
                     "Runtime": "Runtime",
                     "Role": "Role",
                     "Handler": "Handler",
-                    "Code": get_lambda_code_param,
+                    "Code": LambdaFunction.get_lambda_code_param,
                     "Description": "Description",
                     "Environment": get_environment_params,
                     "Timeout": "Timeout",


Test patch:
diff --git a/tests/integration/cloudformation/test_cloudformation_lambda.py b/tests/integration/cloudformation/test_cloudformation_lambda.py
--- a/tests/integration/cloudformation/test_cloudformation_lambda.py
+++ b/tests/integration/cloudformation/test_cloudformation_lambda.py
@@ -1,6 +1,6 @@
 import jinja2
 
-from localstack.utils.common import short_uid
+from localstack.utils.common import short_uid, to_str
 from localstack.utils.generic.wait_utils import wait_until
 from tests.integration.cloudformation.test_cloudformation_changesets import load_template_raw
 
@@ -41,3 +41,44 @@ def test_lambda_autogenerated_name(
     finally:
         cleanup_changesets([change_set_id])
         cleanup_stacks([stack_id])
+
+
+def test_update_lambda_inline_code(
+    cfn_client, lambda_client, is_stack_created, is_stack_updated, cleanup_stacks
+):
+    stack_name = "stack-%s" % short_uid()
+    function_name = "test-func3"
+
+    try:
+        template_1 = jinja2.Template(load_template_raw("lambda_inline_code.yaml")).render(
+            lambda_return_value="hello world"
+        )
+        response = cfn_client.create_stack(
+            StackName=stack_name, TemplateBody=template_1, Capabilities=["CAPABILITY_IAM"]
+        )
+        stack_id = response["StackId"]
+        assert stack_id
+        wait_until(is_stack_created(stack_id))
+
+        rs = lambda_client.get_function(FunctionName=function_name)
+        assert function_name == rs["Configuration"]["FunctionName"]
+        result = lambda_client.invoke(FunctionName=function_name)
+        result = to_str(result["Payload"].read())
+        assert result.strip('" \n') == "hello world"
+
+        template_2 = jinja2.Template(load_template_raw("lambda_inline_code.yaml")).render(
+            lambda_return_value="hello globe"
+        )
+        cfn_client.update_stack(
+            StackName=stack_name, TemplateBody=template_2, Capabilities=["CAPABILITY_IAM"]
+        )
+        wait_until(is_stack_updated(stack_id))
+
+        rs = lambda_client.get_function(FunctionName=function_name)
+        assert function_name == rs["Configuration"]["FunctionName"]
+        result = lambda_client.invoke(FunctionName=function_name)
+        result = to_str(result["Payload"].read())
+        assert result.strip('" \n') == "hello globe"
+    finally:
+        # cleanup
+        cleanup_stacks([stack_name])
diff --git a/tests/integration/docker/__init__.py b/tests/integration/docker_utils/__init__.py
similarity index 100%
rename from tests/integration/docker/__init__.py
rename to tests/integration/docker_utils/__init__.py
diff --git a/tests/integration/docker/conftest.py b/tests/integration/docker_utils/conftest.py
similarity index 100%
rename from tests/integration/docker/conftest.py
rename to tests/integration/docker_utils/conftest.py
diff --git a/tests/integration/docker/test_docker.py b/tests/integration/docker_utils/test_docker.py
similarity index 100%
rename from tests/integration/docker/test_docker.py
rename to tests/integration/docker_utils/test_docker.py
diff --git a/tests/integration/templates/lambda_inline_code.yaml b/tests/integration/templates/lambda_inline_code.yaml
new file mode 100644
--- /dev/null
+++ b/tests/integration/templates/lambda_inline_code.yaml
@@ -0,0 +1,31 @@
+Resources:
+  fnServiceRole5D180AFD:
+    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
+  fn5FF616E3:
+    Type: AWS::Lambda::Function
+    Properties:
+      Code:
+        ZipFile: exports.handler = async (event, context) => {return "{{ lambda_return_value }}"}
+      Role:
+        Fn::GetAtt:
+          - fnServiceRole5D180AFD
+          - Arn
+      FunctionName: test-func3
+      Handler: index.handler
+      Runtime: nodejs14.x
+    DependsOn:
+      - fnServiceRole5D180AFD