Skip to content

Commit

Permalink
Add wkhtmltopdf
Browse files Browse the repository at this point in the history
  • Loading branch information
kalekseev committed May 5, 2020
1 parent 2c64672 commit 4f989df
Show file tree
Hide file tree
Showing 15 changed files with 461 additions and 166 deletions.
84 changes: 72 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@ name: Build and Release
on: push

jobs:
build:
name: Build and Release
weasyprint-build:
name: Build WeasyPrint
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]') "
steps:
- uses: actions/checkout@v2
- name: Build layer
- name: Build weasyprint
run: |
make build/weasyprint-layer-python3.8.zip
- name: Test layer
- name: Test weasyprint
run: |
mkdir weasyprint_output
TEST_FILENAME=weasyprint_output/report.pdf make test.weasyprint
TEST_FILENAME=weasyprint_output/report.png make test.weasyprint
mkdir output
TEST_FILENAME=output/report.pdf make test.weasyprint
TEST_FILENAME=output/report.png make test.weasyprint
- name: Upload pdf
uses: actions/upload-artifact@v1
with:
name: weasyprint_output
path: weasyprint_output
name: WeasyPrint test results
path: output
- name: Create WeasyPrint Release
id: create_release
id: create_weasyprint_release
if: startsWith(github.ref, 'refs/tags/weasyprint-')
uses: actions/create-release@latest
env:
Expand All @@ -31,13 +31,73 @@ jobs:
tag_name: ${{ github.ref }}
release_name: Release WeasyPrint layer
- name: Upload WeasyPrint Layer
id: upload_release_asset
if: startsWith(github.ref, 'refs/tags/weasyprint-')
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
upload_url: ${{ steps.create_weasyprint_release.outputs.upload_url }}
asset_path: ./build/weasyprint-layer-python3.8.zip
asset_name: weasyprint-layer-python3.8.zip
asset_content_type: application/zip

wkhtmltox-build:
name: Build wkhtmltox
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]') "
steps:
- uses: actions/checkout@v2
- name: Build wkhtmltox
run: |
make build/wkhtmltox-layer.zip
make build/wkhtmltopdf-layer.zip
make build/wkhtmltoimage-layer.zip
- name: Test wkhtmltox
run: |
mkdir output
TEST_FILENAME=output/google.pdf make test.wkhtmltox
TEST_FILENAME=output/google.png make test.wkhtmltox
- name: Upload pdf
uses: actions/upload-artifact@v1
with:
name: wkhtmltox test results
path: output
- name: Create wkhtmltox Release
id: create_wkhtmltox_release
if: startsWith(github.ref, 'refs/tags/wkhtmltox-')
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release wkhtmltox layer
- name: Upload wkhtmltox Layer
if: startsWith(github.ref, 'refs/tags/wkhtmltox-')
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_wkhtmltox_release.outputs.upload_url }}
asset_path: ./build/wkhtmltox-layer.zip
asset_name: wkhtmltox-layer.zip
asset_content_type: application/zip
- name: Upload wkhtmltopdf Layer
if: startsWith(github.ref, 'refs/tags/wkhtmltox-')
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_wkhtmltox_release.outputs.upload_url }}
asset_path: ./build/wkhtmltopdf-layer.zip
asset_name: wkhtmltopdf-layer.zip
asset_content_type: application/zip
- name: Upload wkhtmltoimage Layer
if: startsWith(github.ref, 'refs/tags/wkhtmltox-')
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_wkhtmltox_release.outputs.upload_url }}
asset_path: ./build/wkhtmltoimage-layer.zip
asset_name: wkhtmltoimage-layer.zip
asset_content_type: application/zip
82 changes: 56 additions & 26 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,35 @@ TEST_FILENAME ?= report.pdf

.PHONY: stack.deploy.weasyprint clean test.weasyprint

build/weasyprint-layer-$(RUNTIME).zip: layers/weasyprint/builder.sh \
build/cairo-pixbuf-libffi-pango-layer-amazon2.zip \
build/fonts-layer-amazon2.zip \
Makefile \
all: build/weasyprint-layer-$(RUNTIME).zip build/wkhtmltopdf-layer.zip

build/weasyprint-layer-$(RUNTIME).zip: weasyprint/layer_builder.sh \
build/fonts-layer.zip \
| _build
docker run --rm \
-v `pwd`/layers/weasyprint:/out \
-v `pwd`/weasyprint:/out \
-t lambci/lambda:build-${RUNTIME} \
bash /out/builder.sh
mv -f ./layers/weasyprint/weasyprint.zip ./build/weasyprint-intermediate.zip
bash /out/layer_builder.sh
mv -f ./weasyprint/layer.zip ./build/weasyprint-no-fonts-layer.zip
cd build && rm -rf ./opt && mkdir opt \
&& unzip cairo-pixbuf-libffi-pango-layer-amazon2.zip -d opt \
&& unzip fonts-layer-amazon2.zip -d opt \
&& unzip weasyprint-intermediate.zip -d opt \
&& unzip fonts-layer.zip -d opt \
&& unzip weasyprint-no-fonts-layer.zip -d opt \
&& cd opt && zip -r9 ../weasyprint-layer-${RUNTIME}.zip .

build/fonts-layer-amazon2.zip: layers/fonts/builder.sh Makefile | _build
docker run --rm \
-v `pwd`/layers/fonts:/out \
-t lambci/lambda:build-${RUNTIME} \
bash /out/builder.sh
mv -f ./layers/fonts/layer.zip ./build/fonts-layer-amazon2.zip

build/cairo-pixbuf-libffi-pango-layer-amazon2.zip: layers/cairo-pixbuf-libffi-pango/builder.sh Makefile | _build
build/fonts-layer.zip: fonts/layer_builder.sh | _build
docker run --rm \
-v `pwd`/layers/cairo-pixbuf-libffi-pango:/out \
-v `pwd`/fonts:/out \
-t lambci/lambda:build-${RUNTIME} \
bash /out/builder.sh
mv -f ./layers/cairo-pixbuf-libffi-pango/layer.zip ./build/cairo-pixbuf-libffi-pango-layer-amazon2.zip
bash /out/layer_builder.sh ${EXTRA_FONTS}
mv -f ./fonts/layer.zip $@

stack.diff.weasyprint:
stack.diff:
cd cdk-stacks && npm install && npm run build
cdk diff --app ./cdk-stacks/bin/app.js --stack WeasyPrintStack --parameters uploadBucketName=${BUCKET}
cdk diff --app ./cdk-stacks/bin/app.js --stack PrintStack --parameters uploadBucketName=${BUCKET}

stack.deploy.weasyprint:
stack.deploy:
cd cdk-stacks && npm install && npm run build
cdk deploy --app ./cdk-stacks/bin/app.js --stack WeasyPrintStack --parameters uploadBucketName=${BUCKET}
cdk deploy --app ./cdk-stacks/bin/app.js --stack PrintStack --parameters uploadBucketName=${BUCKET}

test.weasyprint:
docker run --rm \
Expand All @@ -52,10 +44,48 @@ test.weasyprint:
lambda_function.lambda_handler \
'{"url": "https://weasyprint.org/samples/report/report.html", "filename": "${TEST_FILENAME}", "return": "base64"}' \
| tail -1 | jq .body | tr -d '"' | base64 -d > ${TEST_FILENAME}
@echo "Check ./report.pdf, eg.: xdg-open report.pdf"
@echo "Check ./${TEST_FILENAME}, eg.: xdg-open ${TEST_FILENAME}"


build/wkhtmltox-layer.zip: wkhtmltox/layer_builder.sh \
build/fonts-layer.zip \
| _build
docker run --rm \
-v `pwd`/wkhtmltox:/out \
-t lambci/lambda:build-${RUNTIME} \
bash /out/layer_builder.sh
mv -f ./wkhtmltox/layer.zip ./build/wkhtmltox-no-fonts-layer.zip
cd build && rm -rf ./opt && mkdir opt \
&& unzip fonts-layer.zip -d opt \
&& unzip wkhtmltox-no-fonts-layer.zip -d opt \
&& cd opt && zip -r9 ../wkhtmltox-layer.zip .

build/wkhtmltopdf-layer.zip: build/wkhtmltox-layer.zip
cp build/wkhtmltox-layer.zip $@
zip -d $@ "bin/wkhtmltoimage"

build/wkhtmltoimage-layer.zip: build/wkhtmltox-layer.zip
cp build/wkhtmltox-layer.zip $@
zip -d $@ "bin/wkhtmltopdf"

test.wkhtmltox:
docker run --rm \
-e FONTCONFIG_PATH="/opt/fonts" \
-v `pwd`/wkhtmltox:/var/task \
-v `pwd`/build/opt:/opt \
lambci/lambda:${RUNTIME} \
lambda_function.lambda_handler \
'{"args": "https://google.com", "filename": "${TEST_FILENAME}", "return": "base64"}' \
| tail -1 | jq .body | tr -d '"' | base64 -d > ${TEST_FILENAME}
@echo "Check ./${TEST_FILENAME}, eg.: xdg-open ${TEST_FILENAME}"


_build:
@mkdir -p build

clean:
rm -rf ./build

fonts.list:
docker run --rm lambci/lambda:build-${RUNTIME} \
bash -c "yum search font | grep noarch | grep -v texlive"
83 changes: 22 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,74 +1,35 @@
# AWS Lambda to print pdfs
# Cloud Print Utils

## WeasyPrint

[WeasyPrint](https://weasyprint.org/) is python based pdf/png print service.

### Lambda Layer

To build a layer you need **docker** installed on your system.
This layer supports only amazon linux 2 runtimes.

Build layer:

$ make build/weasyprint-layer-python3.8.zip

# for future runtimes, eg: python3.9
# RUNTIME=python3.9 make build/weasyprint-layer-python3.9.zip

# to test your build run
$ make test.weasyprint
This is a collection of AWS Lambda layers and functions to render pdf documents
and images from HTML.

Deploy layer (see below for full stack deployment):
Currently solutions based on these tools available:

$ aws lambda publish-layer-version \
--region <region> \
--layer-name <name> \
--zip-file fileb://build/weasyprint-layer-python3.8.zip
- [WeasyPrint](https://weasyprint.org/)
- [wkhtmltopdf](https://wkhtmltopdf.org/)

Environment variables expected by layer:
To build a layer you need **make** and **docker** installed on your system.
The layers support only amazon linux 2 runtimes, eg. python3.8, nodejs12.x.

GDK_PIXBUF_MODULE_FILE="/opt/lib/loaders.cache"
FONTCONFIG_PATH="/opt/fonts"
XDG_DATA_DIRS="/opt/lib"
By default only minimal subset of system fonts [installed](fonts/layer_builder.sh) if you need more fonts
set `EXTRA_FONTS` env variable with space separated list
of font packages to install, eg:

### Lambda Function
EXTRA_FONTS="liberation-fonts xorg-x11-fonts-cyrillic" make build/weasyprint-layer-python3.8.zip

Simple lambda function [provided](weasyprint/lambda_function.py), it requires `BUCKET=<bucket name>` env variable if files stored on s3.
You can search for available font packages with `make fonts.list`.

Example payload to print pdf from url and return link to s3:

{"url": "https://weasyprint.org/samples/report/report.html", "filename": "report.pdf"}

Example paylod to print png from html and css data and return png content encoded in base64:

{"html": "<html><h1>Header</h1></html>", "css": ["h1 { color: red }"], "filename": "report.png", "return": "base64"}

### CloudFormatin Deployment

**WARNING** review [code](cdk-stacks/lib/weasyprint-stack.ts) before deployment.

Stack includes: printer lambda function and s3 bucket where files are stored.

We use [CDK](https://docs.aws.amazon.com/cdk/latest/guide/home.html) to deploy stack, thus you need nodejs installed to run it:
## WeasyPrint

# build
$ cd cdk-stacks && npm install && npm run build
# view diff
$ cdk diff --stack WeasyPrintStack --parameters uploadBucketName=<bucket name>
# deploy
$ cdk deploy --stack WeasyPrintStack --parameters uploadBucketName=<bucket name>
[WeasyPrint](https://weasyprint.org/) is python based pdf/png print service.

To test your deployment:
Run `make build/weasyprint-layer-python3.8.zip` to build a layer, for details
see related [readme](weasyprint/README.md).

# invoke function
$ aws lambda invoke --function-name weasyprint-print \
--payload '{"url": "https://weasyprint.org/samples/report/report.html", "filename": "report.pdf"}' \
--log-type Tail --query 'LogResult' --output text out | base64 -d
## wkhtmltopdf

# view output
$ cat out
{"statusCode": 200, "body": "https://your-bucket.s3.amazonaws.com/report.pdf?signature..."}
[wkhtmltopdf](https://wkhtmltopdf.org/) is a comand line tool that renders HTML
into PDF and various image formats using the Qt WebKit rendering engine.

# open in browser
$ chromium-browser $(cat out | jq .body | tr -d '"')
Run `make build/wkhtmltox-layer.zip` to build a layer, for details
see related [readme](wkhtmltox/README.md).
44 changes: 34 additions & 10 deletions cdk-stacks/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,37 @@
# Example CDK Stack
# Infrastructure as Code example

This is an example app that you can use to deploy aws stacks.
Stack defined in `lib/`, app defined in `bin/`.
We use [CDK](https://docs.aws.amazon.com/cdk/latest/guide/home.html) to deploy stack, thus you need nodejs installed to run it.

## Useful commands
Each stack consist of printer lambda function, layer and s3 bucket where files are stored.
You must build layer before running deploy.

- `npm run build` compile typescript to js
- `npm run watch` watch for changes and compile
- `npm run test` perform the jest unit tests
- `cdk deploy` deploy this stack to your default AWS account/region
- `cdk diff` compare deployed stack with current state
- `cdk synth` emits the synthesized CloudFormation template
We provide two stacks `WeasyPrintStack` and `WkhtmltoxStack`, below is deployment
example of WeasyPrintStack.

**WARNING** review code before deployment.

# build (rebuild must be done on every change in typescript files)
$ cd cdk-stacks && npm install && npm run build

# view diff WeasyPrintStack
$ npm run cdk diff WeasyPrintStack

# deploy WeasyPrintStack
$ npm run cdk deploy WeasyPrintStack --parameters uploadBucketName=<bucket name>

# generate synthesized CloudFormation template
$ npm run cdk synth WeasyPrintStack

To test your deployment:

# invoke function
$ aws lambda invoke --function-name cloud-print \
--payload '{"url": "https://weasyprint.org/samples/report/report.html", "filename": "report.pdf"}' \
--log-type Tail --query 'LogResult' --output text out | base64 -d

# view output
$ cat out
{"statusCode": 200, "body": "https://your-bucket.s3.amazonaws.com/report.pdf?signature..."}

# open in browser
$ chromium-browser $(cat out | jq .body | tr -d '"')
2 changes: 2 additions & 0 deletions cdk-stacks/bin/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import "source-map-support/register";
import * as cdk from "@aws-cdk/core";
import { WeasyPrintStack } from "../lib/weasyprint-stack";
import { WkhtmltoxStack } from "../lib/wkhtmltox-stack";

const app = new cdk.App();
new WeasyPrintStack(app, "WeasyPrintStack");
new WkhtmltoxStack(app, "WkhtmltoxStack");
1 change: 1 addition & 0 deletions cdk-stacks/lib/weasyprint-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class WeasyPrintStack extends cdk.Stack {

this.bucket = new s3.Bucket(this, "WeasyprintBucket", {
bucketName: uploadBucketName.valueAsString,
// documents are stored only for one day
lifecycleRules: [{ expiration: cdk.Duration.days(1) }],
});

Expand Down

0 comments on commit 4f989df

Please sign in to comment.