AWS CDK CI/CD Pipeline - Deployed Lambda returns ClassNotFoundException

Viewed 755

I am trying to build a CI/CD Pipeline with Lambda by AWS CDK. We are using a gradle project here. Additionally, I followed the example documentation. We have two Stacks defined which are APIStack and ApiStackPipeline where APIStack is handled by Lambda_Build and ApiStackPipeline is handled by CDK_BUILD.

We are initializing Lambda function within ApiStack like;

final Function contactFunction = Function.Builder.create(this, "contactFunction").role(roleLambda)
    .runtime(Runtime.JAVA_8)
    .code(lambdaCode)
    .handler("com.buraktas.contact.main.ContactLambda::handleRequest")
    .memorySize(512)
    .timeout(Duration.minutes(1))
    .environment(environment)
    .description(Instant.now().toString()).build();

In this case we are setting lambdaCode parameter with this.lambdaCode = new CfnParametersCode(); same as shown from the documentation (Even though I am not sure how it is getting).

Now we are passing this lambdaCode into ApiStackPipeline which looks like;

IRepository repository = Repository.fromRepositoryName(this, repoName, repoName);
IBucket bucket = Bucket.fromBucketName(this, "codepipeline-api", "codepipeline-api");

PipelineProject lambdaBuild = PipelineProject.Builder.create(this, "ApiBuild")
    .buildSpec(BuildSpec.fromSourceFilename("lambda-buildspec.yml"))
    .environment(BuildEnvironment.builder().buildImage(LinuxBuildImage.STANDARD_4_0).build())
    .build();

PipelineProject cdkBuild = PipelineProject.Builder.create(this, "ApiCDKBuild")
    .buildSpec(BuildSpec.fromSourceFilename("cdk-buildspec.yml"))
    .environment(BuildEnvironment.builder().buildImage(LinuxBuildImage.STANDARD_4_0).build())
    .build();

Artifact sourceOutput = new Artifact();
Artifact cdkBuildOutput = new Artifact("CdkBuildOutput");
Artifact lambdaBuildOutput = new Artifact("LambdaBuildOutput");

Pipeline.Builder.create(this, "ApiPipeline")
    .stages(Arrays.asList(
        StageProps.builder()
            .stageName("Source")
            .actions(Arrays.asList(
                CodeCommitSourceAction.Builder.create()
                    .actionName("Source")
                    .repository(repository)
                    .output(sourceOutput)
                    .build()))
            .build(),
        StageProps.builder()
            .stageName("Build")
            .actions(Arrays.asList(
                CodeBuildAction.Builder.create()
                    .actionName("Lambda_Build")
                    .project(lambdaBuild)
                    .input(sourceOutput)
                    .outputs(Arrays.asList(lambdaBuildOutput)).build(),
                CodeBuildAction.Builder.create()
                    .actionName("CDK_Build")
                    .project(cdkBuild)
                    .input(sourceOutput)
                    .outputs(Arrays.asList(cdkBuildOutput))
                    .build()))
            .build(),
        StageProps.builder()
            .stageName("Deploy")
            .actions(Arrays.asList(
                CloudFormationCreateUpdateStackAction.Builder.create()
                    .actionName("Lambda_CFN_Deploy")
                    .templatePath(cdkBuildOutput.atPath("ApiStackAlfa.template.json"))
                    .adminPermissions(true)
                    .parameterOverrides(lambdaCode.assign(lambdaBuildOutput.getS3Location()))
                    .extraInputs(Arrays.asList(lambdaBuildOutput))
                    .stackName("ApiStackAlfaDeployment")
                    .build()))
            .build()))
    .artifactBucket(bucket)
    .restartExecutionOnUpdate(true)
    .build();

Here I also shared the *-buildspec.yml files;

lambda-buildspec.yml

version: 0.2

phases:
  install:
    runtime-versions:
      java: corretto8
  build:
    commands:
      - echo current directory `pwd`
      - echo building gradle project on `date`
      - ./gradlew clean build
artifacts:
  files:
    - build/distributions/src.zip
  discard-paths: yes

cdk-buildspec.yml

version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 10
      java: corretto8
    commands:
      - echo installing aws-cdk on `date`
      - npm install aws-cdk
  build:
    commands:
      - echo current directory `pwd`
      - ls -l
      - echo building cdk project on `date`
      - ./gradlew clean build
      - npx cdk synth -o dist
  post_build:
    commands:
      - echo listing files after build under dist
      - ls -l dist
artifacts:
  files:
    - ApiStackAlfa.template.json
  base-directory: dist

Here is the exception stack trace I am getting

Class not found: com.buraktas.api.main.Lambda: java.lang.ClassNotFoundException
java.lang.ClassNotFoundException: com.buraktas.api.main.Lambda
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)

And finally here I shared a simplified version of project structure if it helps

├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── buraktas
│   │   │           └── api
│   │   │               ├── main
│   │   │               │   ├── ApiMain.java
│   │   │               │   ├── ApiPipelineStack.java
│   │   │               │   ├── ApiStack.java
│   │   │               │   └── Lambda.java
│   │   │               └── repository
│   │   │                   └── Repository.java
│   │   └── resources
│   │       └── log4j.properties
│   └── test
│       ├── java
│       │   ├── DocumentTest.java
│       │   └── JsonWriterSettingsTest.java
│       └── resources
│           └── request.http

It looks like everything is working fine, Pipeline is getting created successfully and Source -> Build -> Deploy steps are running smoothly. However, when I trigger my lambda function I am getting ClassNotFoundException. I tried both using .zip or .jar (fat jar) artifacts but nothing changed.

Thanks for your help.

1 Answers

I figured out that the problem is happening because CodeBuild creates a zip from given artifact. This means there will be a zip file containing src.zip itself which contains the correct project build files. And since this main zip file is being uploaded to Lambda it is not able to find handler definition so that it throws a ClassNotFoundException. However, this additional zip process is not being mentioned neither in the example documentation nor in the AWS CodeBuild reference documentation for buildspec. We need to manually unzip the contents of zip file and give it as artifact output. Here is the final version of our buildspec.yml. Additionally, if you dont want to deal with unzipping contents then you need to configure your build tool (we are using gradle here) to not zip contents into a zip file after running build command.

version: 0.2

phases:
  install:
    runtime-versions:
      java: corretto8
  build:
    commands:
      - echo current directory `pwd`
      - echo building gradle project on `date`
      - ./gradlew clean build
  post_build:
    commands:
      - mkdir build/distributions/api
      - unzip build/distributions/api.zip -d build/distributions/api
artifacts:
  files:
    - '**/*'
  base-directory: build/distributions/api
Related