node.jsaws-lambdaaws-cdkgraphicsmagick

aws cdk graphicsmagick lambda pdf to png


I require AWS CDK to deploy a lambda fleet and would like to use gm to convert the first page of a PDF to a PNG. I'm okay with the implementation, as it works fine on my laptop, I just need help adding the graphicsmagick binaries.

I'm of course getting Error: Stream yields empty buffer, because the below NodejsFunction does not include the necessary binaries. How do I deploy a NodejsFunction with graphicsmagick installed?

    const designerHandler = new NodejsFunction(this, "designer-server", {
      functionName: "designer-server",
      memorySize: 512,
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: "handler",
      entry: path.join(__dirname, "./server/src/index.ts"),
      timeout: cdk.Duration.seconds(30),
    });

I've tried using layers with no success, and I'm looking into using docker now.

    designerHandler.addLayers(
      lambda.LayerVersion.fromLayerVersionArn(
        this,
        "layer-graphicsmagick",
        "arn:aws:lambda:ap-southeast-2:391641713082:layer:graphicsmagick-layer:1"
      )
    );

Here's my usage with gm.

  const pngOf = (pdf: Buffer) => {
    return new Promise(
      (resolve: (value: Buffer) => void, reject: (reason: any) => void) => {
        gm(pdf)
          .density(600, 600)
          .resize(600)
          .toBuffer('PNG', (err, png) => {
            if (err) {
              reject(err);
            }
            resolve(png);
          });
      }
    );
  };


Solution

  • I ended up solving this with DockerImage function.

    const designerHandler = new lambda.DockerImageFunction(
      this,
      "designer-server",
      {
        functionName: "designer-server",
        memorySize: 512,
        code: lambda.DockerImageCode.fromImageAsset(
          path.join(__dirname, "./server")
        ),
        timeout: cdk.Duration.seconds(30),
      }
    );
    

    The Dockerfile as follows

    # transpile typescript into javascript
    FROM public.ecr.aws/lambda/nodejs:18 as builder
    
    WORKDIR /usr/app
    
    COPY package*.json ./
    RUN npm install
    
    # .dockerignore node_modules/ dist/
    COPY . .
    # rimraf dist && tsc
    RUN npm run build 
    
    
    FROM public.ecr.aws/lambda/nodejs:18
    
    WORKDIR ${LAMBDA_TASK_ROOT}
    
    RUN yum -y install gcc-c++ make libpng-devel libjpeg-devel libtiff-devel wget tar gzip libpng libjpeg libtiff ghostscript freetype freetype-devel jasper jasper-devel
    RUN wget https://sourceforge.net/projects/graphicsmagick/files/graphicsmagick/1.3.38/GraphicsMagick-1.3.38.tar.gz \
            && tar -zxvf GraphicsMagick-1.3.38.tar.gz \
            && rm GraphicsMagick-1.3.38.tar.gz
    RUN ./GraphicsMagick-1.3.38/configure --prefix=/var/task/graphicsmagick --enable-shared=no --enable-static=yes
    RUN make
    RUN make install
    RUN tar -zcvf ~/graphicsmagick.tgz /var/task/graphicsmagick/
    
    ENV PATH="${PATH}:/var/task/graphicsmagick/bin"
    
    COPY package*.json ./
    RUN npm install
    
    # copy builders javascript to task root
    COPY --from=builder /usr/app/dist ./
    ENTRYPOINT [ "/lambda-entrypoint.sh" ]
    CMD [ "index.handler" ]
    

    Usage in handler is then as simple as

    import gm from 'gm';
    

    Here's tsconfig.json incase wanted.

    {
      "compilerOptions": {
        "target": "es2020",
        "module": "commonjs",
        "rootDir": "./src",
        "outDir": "./dist",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "baseUrl": "./src",
        "paths": {
          "src/*": ["./*"]
        },
      },
      "include": ["src/**/*"],
      "exclude": ["node_modules"]
    }
    

    If not using typescript remove the builder altogether and simply COPY usr/app/src ./.