-
-
Save bensie/56f51bc33d4a55e2fc9a to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash | |
# Must be run on an Amazon Linux AMI that matches AWS Lambda's runtime which can be found at: | |
# https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html | |
# | |
# As of May 21, 2019, this is: | |
# Amazon Linux AMI 2018.03.0 (ami-0756fbca465a59a30) | |
# | |
# You need to prepend PATH with the folder containing these binaries in your Lambda function | |
# to ensure these newer binaries are used. | |
# | |
# In a NodeJS runtime, you would add something like the following to the top of | |
# your Lambda function file: | |
# process.env['PATH'] = process.env['LAMBDA_TASK_ROOT'] + '/imagemagick/bin:' + process.env['PATH'] | |
# | |
# This works with both ImageMagick v6.x and v7.x | |
# version=6.9.10-23 | |
version=7.0.8-45 | |
sudo yum -y install libpng-devel libjpeg-devel libtiff-devel gcc | |
curl -O https://imagemagick.org/download/ImageMagick-$version.tar.gz | |
tar zxvf ImageMagick-$version.tar.gz | |
cd ImageMagick-$version | |
./configure --prefix=/var/task/imagemagick --enable-shared=no --enable-static=yes | |
make | |
sudo make install | |
tar zcvf ~/imagemagick.tgz /var/task/imagemagick/ |
I've had success using layers like this on node10.x - https://github.com/serverlesspub/imagemagick-aws-lambda-2 - it worked out of the box, and then with some custom modifications. Going to approach ghostscript the same way.
Updated instructions for nodejs10.x runtime
I was able to get this working. This should also work for nodejs12.x.
ImageMagick
Before proceeding, set up a Lambda Execution Environment. See https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html. The AMI amzn2-ami-hvm-2.0.20190313-x86_64-gp2 was used for this compilation.
- On the server, download the latest version of ImageMagick 6 or 7 from https://imagemagick.org/download/.
- Extract the source.
- Install dependencies:
sudo yum install libpng-devel libjpeg-devel libtiff-devel gcc ImageMagick-devel xz-devel.x86_64 fontconfig-devel.x86_64 libxml2-devel.x86_64 libtool-ltdl-devel lcms2-devel
. - Run
./configure --prefix=/var/task/imagemagick --sysconfdir=/etc --datadir=/usr/share --includedir=/usr/include --libdir=/usr/lib64 --libexecdir=/usr/libexec --localstatedir=/var --sharedstatedir=/var/lib --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared=no --enable-static=yes --with-modules --with-perl=no --with-x=no --with-gslib=no --with-lcms --without-rsvg --with-xml --without-dps --disable-hdri --with-quantum-depth=8 --disable-openmp
in the source directory.- Note the intentional use of
--with-gslib=no
. This forces use of thegs
binary rather than the system library. See this discussion for details.
- Note the intentional use of
- Run
make
in the source directory. - Run
sudo make install
in the source directory. - Copy only the needed built files from the
/var/task/imagemagick/bin
directory into a local clone of your repository.- If building ImageMagick 7, symlinks are present in the bin directory, so be sure to preserve symlinks using
zip --symlinks
ortar zcf
.
- If building ImageMagick 7, symlinks are present in the bin directory, so be sure to preserve symlinks using
- Copy
libbz2.so.1
,libexpat.so.1
,libfontconfig.so.1
,libfreetype.so.6
,libgs.so.9
,libjbig.so.2.0
,libjpeg.so.62
,liblcms2.so.2
,liblzma.so.5
,libpng15.so.15
,libtiff.so.5
, andlibxml2.so.2
tolib/
in your repository from the server in/usr/lib64/
. Be sure to copy the files that these symlink to, not the symlinks.
Then in the Lambda function, above the handler
definition, include the following:
process.env['PATH'] = `${process.env['LAMBDA_TASK_ROOT']}/bin:${process.env['PATH']}`;
process.env['MAGICK_CONFIGURE_PATH'] = `${process.env['LAMBDA_TASK_ROOT']}/etc/ImageMagick`;
I also copied /etc/ImageMagick from the ami instance into my repository and am setting MAGICK_CONFIGURE_PATH
-- I don't know if this is necessary:
process.env['MAGICK_CONFIGURE_PATH'] = `${process.env['LAMBDA_TASK_ROOT']}/etc/ImageMagick`;
Ghostscript (for PDF support)
- Download the latest version of Ghostscript for Linux x86 (64 bit) from https://www.ghostscript.com/download/gsdnld.html.
- Copy the
gs-*-linux-x86_64
executable tobin/gs
in your repository. - Ensure the
bin/gs
binary is executable viachmod +x bin/gs
. - Copy libgs.so.9 to
lib/
from the server in/usr/lib64/
. Be sure to copy the file that this symlinks to, not the symlink.
@chaddjohnson can you please write command for step 7 and 8 I am very new to linux , it will help , I am just stuck in this,
also there is no libexpat library in usr/lib64
@singhadarsh93 Should be something like this:
-
Run approximately these commands:
On the server:
cd /var/task/imagemagick tar zcf bin.tar.gz bin/convert bin/composite bin/identify
On your computer (replace xxx.xxx.xxx.xxx with the server IP):
ssh -i key.pem [email protected] 'cat /var/task/imagemagick/bin.tar.gz' > bin.tar.gz tar zxf bin.tar.gz
Then there should be a
bin
directory on your local machine with the binaries. -
Run approximately these commands:
On the server:
mkdir /home/ec2-user/lib cd /usr/lib64 cp -L libbz2.so.1 libexpat.so.1 libfontconfig.so.1 libfreetype.so.6 libgs.so.9 libjbig.so.2.0 libjpeg.so.62 liblcms2.so.2 liblzma.so.5 libpng15.so.15 libtiff.so.5 and libxml2.so.2 /home/ec2-user/lib/ cd /home/ec2-user tar zcf lib.tar.gz lib/
On your computer:
ssh -i key.pem [email protected] 'cat lib.tar.gz' > lib.tar.gz tar zxf lib.tar.gz
Then there should be a
lib
directory on your local machine with the .so files.
Not sure why libexpat isn't present. Maybe try yum install expat-devel
. If that doesn't work, try searching via yum search expat
, and install the expat devel 64-bit package.
var IM_PATH = process.env['LAMBDA_TASK_ROOT'] + "/imagemagick/bin/"; process.env['LD_LIBRARY_PATH'] = '/var/task/imagemagick/lib/'; process.env['PATH'] = process.env['PATH'] + ':' + IM_PATH; var gm = require('gm').subClass({ imageMagick: true, appPath: '/var/task/imagemagick/bin/', });
remember to put the imagemagick floder in the root folder
I tried this after installing imagemagick in AWS lambda, but it automatically appends the path with "identify". And I dont know, where is that written.
could you share the final stack of files?
Instructions for Python 3.8
My use-case was to convert pages in a PDF to PNG via wand. I followed the instructions provided by @chaddjohnson at https://gist.github.com/bensie/56f51bc33d4a55e2fc9a#gistcomment-3133859 but was getting errors when using wand. Following are the instructions on how I was able to resolve those by doing some slight adjustments:
- Start an EC2 instance and SSH into it. I used the AMI
amzn2-ami-hvm-2.0.20210126.0-x86_64-gp2
. - Download ImageMagick 6.9.11.
wget https://download.imagemagick.org/ImageMagick/download/ImageMagick-6.9.11-60.tar.gz
- Extract the folder.
tar zxvf ImageMagick-6.9.11-60.tar.gz
cd
into the extracted folder.cd ImageMagick-6.9.11-60
- Edit the
policy.xml
file to allow PDF to PNG conversion.I copy-pasted the following content but you can modify it as needed.nano config/policy.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE policymap [ <!ELEMENT policymap (policy)+> <!ELEMENT policy (#PCDATA)> <!ATTLIST policy domain (delegate|coder|filter|path|resource) #IMPLIED> <!ATTLIST policy name CDATA #IMPLIED> <!ATTLIST policy rights CDATA #IMPLIED> <!ATTLIST policy pattern CDATA #IMPLIED> <!ATTLIST policy value CDATA #IMPLIED> ]> <!-- Configure ImageMagick policies. Domains include system, delegate, coder, filter, path, or resource. Rights include none, read, write, and execute. Use | to combine them, for example: "read | write" to permit read from, or write to, a path. Use a glob expression as a pattern. Suppose we do not want users to process MPEG video images: <policy domain="delegate" rights="none" pattern="mpeg:decode" /> Here we do not want users reading images from HTTP: <policy domain="coder" rights="none" pattern="HTTP" /> Lets prevent users from executing any image filters: <policy domain="filter" rights="none" pattern="*" /> The /repository file system is restricted to read only. We use a glob expression to match all paths that start with /repository: <policy domain="path" rights="read" pattern="/repository/*" /> Let's prevent possible exploits by removing the right to use indirect reads. <policy domain="path" rights="none" pattern="@*" /> Any large image is cached to disk rather than memory: <policy domain="resource" name="area" value="1GB"/> Define arguments for the memory, map, area, width, height, and disk resources with SI prefixes (.e.g 100MB). In addition, resource policies are maximums for each instance of ImageMagick (e.g. policy memory limit 1GB, -limit 2GB exceeds policy maximum so memory limit is 1GB). --> <policymap> <!-- <policy domain="resource" name="temporary-path" value="/tmp"/> --> <policy domain="resource" name="memory" value="256MiB"/> <policy domain="resource" name="map" value="512MiB"/> <policy domain="resource" name="width" value="16KP"/> <policy domain="resource" name="height" value="16KP"/> <policy domain="resource" name="area" value="128MB"/> <policy domain="resource" name="disk" value="1GiB"/> <!-- <policy domain="resource" name="file" value="768"/> --> <!-- <policy domain="resource" name="thread" value="4"/> --> <!-- <policy domain="resource" name="throttle" value="0"/> --> <!-- <policy domain="resource" name="time" value="3600"/> --> <!-- <policy domain="system" name="precision" value="6"/> --> <!-- not needed due to the need to use explicitly by mvg: --> <!-- <policy domain="delegate" rights="none" pattern="MVG" /> --> <!-- use curl --> <policy domain="delegate" rights="none" pattern="URL" /> <policy domain="delegate" rights="none" pattern="HTTPS" /> <policy domain="delegate" rights="none" pattern="HTTP" /> <!-- in order to avoid to get image with password text --> <policy domain="path" rights="none" pattern="@*"/> <policy domain="cache" name="shared-secret" value="passphrase" stealth="true"/> <!-- disable ghostscript format types --> <policy domain="coder" rights="none" pattern="PS" /> <policy domain="coder" rights="none" pattern="EPI" /> <policy domain="coder" rights="read|write" pattern="PDF" /> <policy domain="coder" rights="none" pattern="XPS" /> <policy domain="coder" rights="read|write" pattern="LABEL" /> </policymap>
- Configure and install ImageMagick.
./configure --prefix=/var/task/imagemagick --sysconfdir=/etc --datadir=/usr/share --includedir=/usr/include --libdir=/usr/lib64 --libexecdir=/usr/libexec --localstatedir=/var --sharedstatedir=/var/lib --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared=no --enable-static=yes --with-modules --with-perl=no --with-x=no --with-gslib=no --with-lcms --without-rsvg --with-xml --without-dps --disable-hdri --with-quantum-depth=8 --disable-openmp make sudo make install
- Copy the required
.so
files.Copy themkdir lib cd /usr/lib64/ cp -L libbz2.so.1 libexpat.so.1 libfontconfig.so.1 libfreetype.so.6 libgs.so.9 libjbig.so.2.0 libjpeg.so.62 liblcms2.so.2 liblzma.so.5 libpng15.so.15 libtiff.so.5 libxml2.so.2 libMagickCore-6.Q16.so.6 libMagickWand-6.Q16.so.6 libXext.so.6 libXt.so.6 libltdl.so.7 libSM.so.6 libICE.so.6 libX11.so.6 libgomp.so.1 libuuid.so.1 libxcb.so.1 libXau.so.6 libMagickCore-6.Q8.so.6 libMagickWand-6.Q8.so.6 libm.so.6 libz.so.1 libjasper.so.1 /home/ec2-user/lib/ cp -r ImageMagick-6.9.10/ ImageMagick-6.9.11/ /home/ec2-user/lib/ cd /home/ec2-user tar zcf lib.tar.gz lib/
lib.tar.gz
file from the server to your local machine. - Copy the required binary files.
Copy the
cd /var/task/imagemagick sudo tar zcf bin.tar.gz bin/ cp bin.tar.gz /home/ec2-user/bin.tar.gz
bin.tar.gz
file from the server to your local machine. - Copy the XML files required by ImageMagick.
Copy the
cd /etc/ sudo tar zcf etc.tar.gz ImageMagick-6/ cp etc.tar.gz /home/ec2-user/etc.tar.gz
etc.tar.gz
file from the server to your local machine. - Close the SSH session.
- On your local machine, extract the contents of the 3
*.tar.gz
files. - Download ghostscript from https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs9533/ghostscript-9.53.3-linux-x86_64.tgz and extract the ghostscript binary into the
bin/
folder and rename it togs
. Runchmod +x bin/gs
to make it executable. - Compress the 3 -
lib
,bin
andetc
- folders into a ZIP file. The tree structure of the ZIP file would look like
I have usedfile.zip/ |-- bin | |-- convert | |-- ... | `-- gs |-- etc | `-- ImageMagick-6 | |-- coder.xml | |-- ... | `-- type.xml `-- lib |-- ImageMagick-6.9.10 | |-- config-Q16 | | `-- configure.xml | `-- modules-Q16 | |-- coders | | |-- aai.la | | |-- ... | | `-- yuv.so | `-- filters | |-- analyze.la | `-- analyze.so |-- ImageMagick-6.9.11 | |-- config-Q8 | | `-- configure.xml | `-- modules-Q8 | |-- coders | | |-- aai.la | | |-- ... | | `-- yuv.so | `-- filters | |-- analyze.la | `-- analyze.so |-- libICE.so.6 |-- ... `-- libz.so.1
...
wherever the folder contained more than 2 files to denote that there are more files present. - Create a Python 3.8 runtime compatible layer on AWS Lambda and use the ZIP created in step 13.
- Add the layer to your AWS Lambda function code.
- Update environment variables in your lambda function.
import os os.environ["PATH"] = f"/opt/bin:{os.environ['PATH']}" os.environ["LD_LIBRARY_PATH"] = f"/opt/lib:{os.environ['LD_LIBRARY_PATH']}" os.environ["MAGICK_HOME"] = "/opt/" os.environ["WAND_MAGICK_LIBRARY_SUFFIX"] = "-6.Q8" os.environ["MAGICK_CONFIGURE_PATH"] = "/opt/etc/ImageMagick-6/" os.environ["MAGICK_CODER_MODULE_PATH"] = "/opt/lib/ImageMagick-6.9.11/modules-Q8/coders/"
Note: If the size of the uncompressed ZIP file is too large and you reach AWS Lambda size limits, remove the binaries that you don't need from the bin/
folder. In my case, I only kept Magick-config
, MagickCore-config
, MagickWand-config
, Wand-config
, convert
and gs
and removed others.
NOTE: I also have a ready-to-use ZIP file uploaded at https://github.com/samkit-jain/aws-lambda-imagemagick-ghostscript
Where to Place this Code ?
- Update environment variables in your lambda function.
import os
os.environ["PATH"] = f"/opt/bin:{os.environ['PATH']}"
os.environ["LD_LIBRARY_PATH"] = f"/opt/lib:{os.environ['LD_LIBRARY_PATH']}"
os.environ["MAGICK_HOME"] = "/opt/"
os.environ["WAND_MAGICK_LIBRARY_SUFFIX"] = "-6.Q8"
os.environ["MAGICK_CONFIGURE_PATH"] = "/opt/etc/ImageMagick-6/"
os.environ["MAGICK_CODER_MODULE_PATH"] = "/opt/lib/ImageMagick-6.9.11/modules-Q8/coders/"
@R-Harishkumar You may place the code at the top of the Python file.
I created the layer with your ZIP file and added it to my lambda, along with the code from step 16,
but when I try to do: from wand.image import Image
I get this error: Unable to import module 'lambda_function': No module named 'wand'
And if I try to package wand myself and add it then I get the error where wand doesn't find the shared library for ImageMagick.
Is there any other step you took to get Wand working in AWS Lambda?
Hi @ClickheadZ Adding the ZIP file as a layer would give you the required libraries that you want at the OS level to communicate with imagemagick, gs, ... You still need the pip package files in your lambda archive. Did you add Wand
as a dependency in your lambda function archive? Example here.
@samkit-jain Thanks for the response! I had not added it properly, I repackaged and deployed a zip now and I'm getting a different error, not sure what I did wrong.
Here are the steps I took to package Wand:
- Connect to an EC2 instance
- pip install --target=package wand, pip install --target=package magickwand
- scp the package to my computer, and zip the package along with my lambda function as per AWS zip deployment instructions
In lambda console, my lambda structure now looks like this:
myLambdaFunction
- magickwand
- magickwand-0.2.dist-info
- wand
- Wand-0.6.7.dist-info
lambda_function.py
And when i try from wand.image import Image
I now get the error message : Unable to import module 'lambda_function': MagickWand shared library not found.\nYou probably had not installed ImageMagick library
despite the fact that I have deployed the imagemagick/ghostscript zip as a layer for this lambda and added it. Did I do something wrong in the Wand packaging or is the problem my lambda layer?
@ClickheadZ Facing same issue, were you able to find a fix?
Thanks~ It's work for me !!! But I found a small problem while using it:
curl download url: curl -O https://imagemagick.org/download/ImageMagick-$version.tar.gz , it will return 404 http status...
the ture url is: https://download.imagemagick.org/archive/ImageMagick-$version.tar.xz
@adnascent the
ldd convert
command ended up missing some libraries for me, such aslibxml2.so.2
which is not listed as a shared lib for eitherconvert
orgs
. Were you also checking shared libs for any other library?I've been trying to get this working over the past couple of weeks on node 10 (also tried downgrading to node 8 and got the same errors as @ronaksavla). I'm considering switching gears and trying this with Lambda Layers. Has anyone tried with Layers before?