awslabs/aws-cfn-template-flip

Cleaning a template that contains a lamba function makes the code unreadable

reidca opened this issue · 3 comments

The lambda function

LambdaDeployFunction:
    Type: AWS::Lambda::Function
    DependsOn: LambdaDeployFunctionExecutionRole
    Properties:
      Handler: "index.handler"
      Role: !GetAtt LambdaDeployFunctionExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          import boto3
          import json
          import logging
          import cfnresponse
          import time
          from botocore.exceptions import ClientError

          def handler(event, context):
            logger = logging.getLogger()
            logger.setLevel(logging.INFO)
            logger.info (f"Input parameters from cloud formation: {event}")
            responseData = {}
            if (event["RequestType"] == 'Delete'):
              logger.info("Responding to delete event...")
              cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
            
            try:            
              lambdaClient = boto3.client('lambda')
              s3Bucket = event['ResourceProperties']['S3Bucket']
              s3Key = event['ResourceProperties']['S3Key']
              functionName = event['ResourceProperties']['FunctionName']
              logger.info("Updating the function code for Lambda function '{}' to use the code stored in S3 bucket '{}' at key location '{}'".format(functionName, s3Bucket, s3Key))
              logger.info("Sleeping for 5 seconds to allow IAM permisisons to take effect")
              time.sleep(5)             
              response = lambdaClient.update_function_code(
                FunctionName=functionName,
                S3Bucket='{}'.format(s3Bucket),
                S3Key='{}'.format(s3Key),
                Publish=True)
              responseValue = "Function: {}, Version: {}, Last Modified: {}".format(response["FunctionName"],response["Version"],response["LastModified"])
              responseData['Data'] = responseValue
              cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, response["FunctionArn"])
            except ClientError as e:
              errorMessage = e.response['Error']['Message']
              logger.error(errorMessage)
              cfnresponse.send(event, context, cfnresponse.FAILED, responseData)
      Runtime: "python3.6"
      Timeout: "30"

Was converted to:

LambdaDeployFunction:
    Type: AWS::Lambda::Function
    DependsOn: LambdaDeployFunctionExecutionRole
    Properties:
      Handler: index.handler
      Role: !GetAtt 'LambdaDeployFunctionExecutionRole.Arn'
      Code:
        ZipFile: !Sub "import boto3\nimport json\nimport logging\nimport cfnresponse\n\
          import time\nfrom botocore.exceptions import ClientError\n\ndef handler(event,\
          \ context):\n  logger = logging.getLogger()\n  logger.setLevel(logging.INFO)\n\
          \  logger.info (f\"Input parameters from cloud formation: {event}\")\n \
          \ responseData = {}\n  if (event[\"RequestType\"] == 'Delete'):\n    logger.info(\"\
          Responding to delete event...\")\n    cfnresponse.send(event, context, cfnresponse.SUCCESS,\
          \ responseData)\n  \n  try:            \n    lambdaClient = boto3.client('lambda')\n\
          \    s3Bucket = event['ResourceProperties']['S3Bucket']\n    s3Key = event['ResourceProperties']['S3Key']\n\
          \    functionName = event['ResourceProperties']['FunctionName']\n    logger.info(\"\
          Updating the function code for Lambda function '{}' to use the code stored\
          \ in S3 bucket '{}' at key location '{}'\".format(functionName, s3Bucket,\
          \ s3Key))\n    logger.info(\"Sleeping for 5 seconds to allow IAM permisisons\
          \ to take effect\")\n    time.sleep(5)             \n    response = lambdaClient.update_function_code(\n\
          \      FunctionName=functionName,\n      S3Bucket='{}'.format(s3Bucket),\n\
          \      S3Key='{}'.format(s3Key),\n      Publish=True)\n    responseValue\
          \ = \"Function: {}, Version: {}, Last Modified: {}\".format(response[\"\
          FunctionName\"],response[\"Version\"],response[\"LastModified\"])\n    responseData['Data']\
          \ = responseValue\n    cfnresponse.send(event, context, cfnresponse.SUCCESS,\
          \ responseData, response[\"FunctionArn\"])\n  except ClientError as e:\n\
          \    errorMessage = e.response['Error']['Message']\n    logger.error(errorMessage)\n\
          \    cfnresponse.send(event, context, cfnresponse.FAILED, responseData)\n"
      Runtime: python3.6
      Timeout: '30'

I think it should leave the code as it is because it is unreadable now and impossible to maintain.

Yep, you're completely right there! I'll look into this shortly.

I've got a (partial) solution for this. It doesn't work combining longform pipe-strings with !Sub and friends, but for this template:

{
    "LambdaDeployFunction": {
        "Type": "AWS::Lambda::Function",
        "DependsOn": "LambdaDeployFunctionExecutionRole",
        "Properties": {
            "Handler": "index.handler",
            "Role": {
                "Fn::GetAtt": [
                    "LambdaDeployFunctionExecutionRole",
                    "Arn"
                ]
            },
            "Code": {
                "ZipFile": "import boto3\nimport json\nimport logging\nimport cfnresponse\nimport time\nfrom botocore.exceptions import ClientError\n\ndef handler(event, context):\n  logger = logg
            },
            "Runtime": "python3.6",
            "Timeout": "30"
        }
    }
}

It will correctly use a pipe-string for the ZipFile.

LambdaDeployFunction:
  Type: AWS::Lambda::Function
  DependsOn: LambdaDeployFunctionExecutionRole
  Properties:
    Handler: index.handler
    Role: !GetAtt 'LambdaDeployFunctionExecutionRole.Arn'
    Code:
      ZipFile: |
        import boto3
        import json
        import logging
        import cfnresponse
        import time
        from botocore.exceptions import ClientError

        def handler(event, context):
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)
          logger.info (f"Input parameters from cloud formation: {event}")
          responseData = {}
          if (event["RequestType"] == 'Delete'):
            logger.info("Responding to delete event...")
            cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)

          try:
            lambdaClient = boto3.client('lambda')
            s3Bucket = event['ResourceProperties']['S3Bucket']
            s3Key = event['ResourceProperties']['S3Key']
            functionName = event['ResourceProperties']['FunctionName']
            logger.info("Updating the function code for Lambda function '{}' to use the code stored in S3 bucket '{}' at key location '{}'".format(functionName, s3Bucket, s3Key))
            logger.info("Sleeping for 5 seconds to allow IAM permisisons to take effect")
            time.sleep(5)
            response = lambdaClient.update_function_code(
              FunctionName=functionName,
              S3Bucket='{}'.format(s3Bucket),
              S3Key='{}'.format(s3Key),
              Publish=True)
            responseValue = "Function: {}, Version: {}, Last Modified: {}".format(response["FunctionName"],response["Version"],response["LastModified"])
            responseData['Data'] = responseValue
            cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, response["FunctionArn"])
          except ClientError as e:
            errorMessage = e.response['Error']['Message']
            logger.error(errorMessage)
            cfnresponse.send(event, context, cfnresponse.FAILED, responseData)
    Runtime: python3.6
    Timeout: '30'

See #52 for the code fix

Thanks @ryansb! I left a comment on the PR with a small change but otherwise this looks to solve the issue. Closing :)