複数の画像から印刷用のpdfファイルを作成する(Python, ImageMagick)
複数の画像をまとめて1回の印刷で対応できるような PDF ファイルを作りたい場合にどうするか。
Python上でプログラムによって pdf ファイルを生成する
AWS Lambda Python で PDF 作りたくてどうしようかと思って調べていたけど、Pillow(PIL) と reportlab を使えば Python プログラムで画像連結をしただけのPDFファイルは結構簡単に作れる。
''' 検証環境: virtual env on Python 3.6.5 $ pip install Pillow reportlab $ pip list Package Version ---------- ------- Pillow 5.2.0 pip 10.0.1 reportlab 3.4.0 setuptools 39.2.0 wheel 0.31.1 ''' from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import A4 from PIL import Image # A4 サイズの PDF ファイルを定義する pdf = canvas.Canvas('output.pdf', pagesize=A4) pdf.setPageSize((1116, 1560)) # 1 page を何 pixel で表現するかを 2-Tuple で指定 # ページの左下をoriginとして、座標(0, 0) を起点に画像を読み込み現在のページ内に書き込む pdf.drawInlineImage(Image.open('test001.png'), 0, 0) # 改ページ pdf.showPage() # 次のページにも同様に書き込む pdf.drawInlineImage(Image.open('test002.png'), 0, 0) # ファイルを保存 pdf.save()
setPageSize と 描画座標によってページ内のImage配置が結構柔軟にできる。
基本は縦横一杯だが、画像が大きすぎたり、印刷の都合で上下左右の空白を入れたい場合などはわざとサイズと座標をずらすことで細かい調整が可能。
ImageMagick を使う
本当に何でも変換してくれますねこの子。
とりあえず複雑なことしないなら、こっちの方がすごく楽。
大体のOSで*1Package Managerによってダウンロードできるし。
convert -density 150 -geometry 1240x1754 page01.png page02.png output.pdf
A4で印刷することを前提にしたいので、画像全体を紙全体に貼り付けるために解像度と紙サイズを指定する。
- A4 = 210 x 297 mm
- 画像dpi想定 = 150
- 1 inch = 25.4 mm (see: http://kijie.com/compute/manual/dpi/)
- pixel number := inch x (dot per inch)
- width : inch(210 mm) x 150 dpi = (210 / 25.4) x 150 (-> 1240)
- height: inch(297 mm) x 150 dpi = (297 / 25.4) x 150 (-> 1754)
色がうまく出ないときは -colorspace オプションで RGB/CMYK 変換をかけてもよいかと。
同じようなことをまとめてくれている人がいるのでこちらで。
- https://www.benricho.org/size_scale/pixel_conv.html
- https://www.lbtora.net/imagemagick/20160111/
- https://blog.katsubemakito.net/macos/join_many_pdf_width_imagemagick
(補足) LambdaでImageMagickつかえるんじゃね?
AWS Lambda で ImageMagick を使うのであれば npm の gm とかを使う方法がネット上の公式ドキュメントには書いてある。
ただ、gm の Getting started には imagemagick か graphicsmagick を先にインストールしておけって書いてあるけれどこれがAWS Lambdaの実行環境内にプレインストールされているっていう証跡を記事の記載時点では見つけられなかった。
まあ、世の中の Node Lambda の記事にこれを別途ビルドしてLambda Fucntion内に同時にアップロードするっていう記事が書いていないので、多分node.js用のLambda内部にはインストール済みなんだと思うのだけれど。 あと、Pythonだと PIL での画像処理記事ばかりでるから、このインストールが仮にあったとしても、node.js runtime でしか使えない気がする。
MFAによって認証された有効期限付きのAWS Access Keyをできるだけ楽に使いたい
AWSのIAMユーザーを作って権限を与えて管理している場合、やっぱりふとしたミスが怖い。
例えば、AccessKeyとSecretAccessKeyをプログラムの中に埋め込んでしまっていることを忘れて public repository にコミットしてしまったりなど。
そもそも、profileをちゃんと使って、ソースコードの中に決して埋め込まないようにしたり、IAM Roleとかを使えという話ではあるが、扱う人のAWSレベルによってはこのあたりが理解されず、また、古い資料にはプラクティスを反映していないものがあるので、それらを鵜呑みにしてしまうこともあるので、どうしても問題はなくならない。
なので、複数人で同一アカウントを利用し、ローカルでの個人利用をする場合はコンソールへのログインだけではなく、APIコールもMFA承認をしなくてはつかえないようにすることができる。 具体的には、以下のように Policy の Condition で aws:MultiFactorAuthPresent の有無による制御をかけられる。
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/tutorial_users-self-manage-mfa-and-creds.html
この設定を行った場合、AccessKeyによるProgram Accessを利用する場合はまずsts:GetSessionTokenでMFAコードを渡し、一時的に有効なトークンを取得しなくてはいけない。 取得の方法や使い方なんかは例えば以下のページなどでまとめられている。
https://dev.classmethod.jp/cloud/aws/mfa-protected-aws-api/
が、取得したこれらの認証情報を使いたいのだけれども特定プロファイルに対して逐一自分で保存をしなければいけないのがそろそろ面倒になってきたので、スクリプト1個で何とかできるように以下のようなコードを書いてみた。
https://github.com/a-hisame/aws-mfa-credential-settings
まあ、集団でAccessKeyをちゃんと管理するのであれば、例えばVault使うとかの方が良いと思うけれど、ほぼ個人利用でAccessKeyの流出被害を最低限に抑えるための自己満足として。
AWS LambdaにアップロードしたZIPファイルの取得方法
ちょっとした作業ミスで Lambda 関数をデプロイした後にローカルのファイルを跡形も無くすっ飛ばしたので、現在アップロード済みのファイルを取得できないか調べた。
結論としてはできる。
aws lambda get-function --function-name [your function name]
とした結果の Code.Location にアップロード先のURLが格納されているのでここからダウンロードできる。 後は何も考えずに "curl -o lambda.zip -L [指定のURL]" で落ちてくる (多分有効期限付きURL)
Amazon Simple Workflow ServicesをAWSCLIで使ってみて理解する
ちょうどAWS上でワークフローが必要になるような業務を実装することになったので、アーキテクチャを考えてみました。
もちろん、それらのワークフローの実施が冗長化されており、同時にスケールすることも前提で。
…と、意気込んで以下のコンセプトを元にしたアーキテクチャ図を考えていました。
- 1つの処理の流れの状態を常に持っている (どこまで進んだか、それぞれ中間でどのような結果となったか、など)
- それぞれの処理はLambdaで実装し、上記で示した状態を受け取る
- Lambdaも処理全体をコントロールするControllerと、具体的なサブタスクを実行するSubfunctionsに分ける
- 各Lambda Function自体は入力のみに依存した処理を実行できるのでステートレスを保てる
- Lambda Functionの終了時に後続作業がある場合は、入力を変更したメッセージを用意したSQSに投げる。 この時、必要であればDelayオプションを使って実行を遅延させる
- 処理途中のSQSのメッセージキューはポーリングされており、適切なタイミングで拾われ、再度Lambdaを実行させる (Lambda側では状態に基づいた処理を行い、何も無くなったら処理を終わらせる)
理論的には実装できなくもないですが、問題も数多く有ります。
例えば、次のようなものです。
- SQSのメッセージは2度以上取得される可能性があるため、実行を1度のみに保証する場合の作りこみが大変
- SQSの順序保証が無いため、実行順序が重要な場合は使いづらい
- 1つの処理の流れが何処まで進んだかの可視化が難しい
どうしたものか…と悩んでいたのですが、前々からやけに概念を理解するのが難しそうで避けていたAmazon Simple Workflow Services (SWF) とほとんどそっくりな考え方であったのと、SQSの性質に基づく問題が解決できるということもあったので、本腰を入れてSWFを調べてみることにしました。
日本語の資料が少ないのもあれですが、SWFを紹介している資料は多くの場合はAWS Flow FrameworkというSWFを実装するためのライブラリを利用してSWFを紹介しているものが多かったです。
ただ、これだと個人的には非常に理解しづらかったので、AWSCLIから(ほぼ)APIレベルで機能を使ってみつつ、SWFの仕組みの理解をしようと試みました。
結果としては、APIだけを使ってみると非常にシンプルなできになっている上に、非常に汎用的で使い道も数多くありそう、という結論が得られました。
Amazon Cognito User Pools のAuthorize APIを結局解読した
1度挫折したんですが、Node.js側からせめてサーバーサイドでも認証できました。
具体的なコードを(ほとんどコピーですが)以下のプロジェクトに上げました。
https://github.com/a-hisame/cognito-userpools-example
cognito-idpのGetAuthenticationDetailsとAuthenticateをそれぞれ定義して使っています。
詳しくはプロジェクト内の feature-list-ja.md あたりを読んでみてください。
これで、
- ユーザの生成(+確認用コード付き)
- ユーザのパスワードなどの再発行
- ユーザの管理
- アクティブユーザの閲覧
- ユーザに紐付く一時IAMの発行
Amazon Cognito User Pools をPythonから利用しようとして挫折した
先週のAWS Summit in シカゴで発表された AWS Cognito User Pools ですが、モバイルやウェブ画面からだけではなくて、サーバー側の認証機構として使えないかをPythonとAWSCLIで色々試行錯誤して失敗したので記録しておきます。
原理的にはできなくもないんですが、現在提供されているソースコードを使わない場合は生のAPIを読み解いてゴリゴリ実装する必要がありそうなので、自分は調査を打ち切りました。
現在専用の命令が提供されていない言語でも同じことが言えると思います。
できたこと
そもそものUserPoolsの設定は こちらの記事を参照 して設定しました。
1点注意事項として、Appを設定するときに"Generate Client Secret"の設定がデフォルトでONになっていますが、
JavaScriptや他のAPIコールの場合まだSecret ONの場合に対応していないようなのでOFFにしましょう。
この設定を行った後に、
- ユーザを作る (http://docs.aws.amazon.com/cli/latest/reference/cognito-idp/sign-up.html)
- 作ったユーザの認証をする (http://docs.aws.amazon.com/cli/latest/reference/cognito-idp/confirm-sign-up.html)
までは特に問題なく実施できました。
できなかったこと
- 作ったユーザでログインする
要は、作ったユーザとパスワードでログインして、そのログインセッションIDを取得するオペレーションがやりたかったのです。
アプリケーションを作る場合、ユーザやパスワードはアプリケーションのDB内に持つことが多いですが、
それを代替するサービスとしてCognito UserPoolsが使えないかを検証していました。
原因
- 2016-04-25時点で公式に開示されていない"AWSCognitoIdentityProviderService.Authenticate"というAWS APIをコールしている。
- 呼び出しの詳細は Android SDK内のソースコードをたどった実装から
- boto3の"cognito-idp"は2016-04-18時点のものが最新(version 1.3.1)。 利用可能なAPIは ここ にJSON形式で記載されているが、この中に "Authenticate" APIの定義はない。
- 上記の定義が参照しているcognito-idpのAPIで公開されているリスト (2016-04-18の仕様ではここに Authenticate は存在しない)
- なお、Javaのロジックを見る限り、パスワードは何らかの暗号化をして送付する必要があるっぽい
感想
追記
node.jsですがリベンジしました - http://d.hatena.ne.jp/a-hisame/20160502/1462194635
AWS LambdaでVPC内のPrivateインスタンスにSSH接続する
http://aws.typepad.com/aws_japan/2016/02/access-resources-in-a-vpc-from-your-lambda-functions.html
昨年のAWS re:Inventで対応が発表されていましたが、とうとうAWS LambdaからVPC内リソースにアクセスが出来るようになったので何が出来るのか眺めていました。
そうすると、どうやら「VPC内にNetworkInterfaceを作ることでVPC内リソースにアクセスする」とのことだったので、もしかするとこれはLambdaの起動インスタンスからフリーダムな事ができるのではないかと思い、ちょっと検証してみることにしました。
やりたかったこと
LambdaからVPC内の(インターネットに公開していないインスタンスに)SSH接続して、その内部の情報を拾う/書き換えることが出来ないかを検証する。
今まで、AWS上のAPIで出来ることは基本的に "インスタンスの外側" を制御するものがほとんどであり、"インスタンスの内側" の制御を行う仕組みは基本的に存在していませんでした。
EC2のUserDataを使うなどすれば一時的には利用できますが、Launch時のみに限定されます。
これまでの機能だけでpush型で外部から定期的に制御を行うモデルを構築しようとすると、その制御システムを冗長化する必要があって料金が高くついたり、ある時間のジョブをどれか1つのマシンだけが実行する制御が必要になる場合があるなど、技術的・コスト的にペイさせるのが難しいという問題がありました。 一応、中身を操作するサービスとして CodeDeployがあるにはありましたが、アレはあれでエージェントのインストールが必要になるなど、そこそこ準備側のコストが高いように感じてました。
しかし、AWS Lambda + スケジューラ (or 外部トリガ)によるSSH通信によるプッシュ処理が仮に可能になると、"インスタンスの内側" の制御を堅牢*1に、安価に*2実行できる可能性が高まります。
ぱぱっと検証
私がPythonに慣れているので、PythonでのAWS Lambda Functionを組みました。
その中で、最初は subprocess.call('/bin/ping -c 1 [target]') のように pingコマンドを使おうとしたのですが、Lambdaの上では /bin/ 以下にすらアクセス権限がありませんでした (当たり前といえば当たり前ですが)
そこで、Pythonコードをアップロードするときに周辺のライブラリがアップロードできることを利用して、ssh接続を行うfabric (と、内部で利用しているライブラリ群) をアップロードして使うことにしました。
ハマったところ
使うモジュール群は "pip install fabric -t ." でローカルに落としてこれるのですが、何故か "pycrypto" だけがこのコマンドでやっても落ちてこない。 明示しても /tmp 以下にファイルが存在しないと言われて失敗する。
これは "sudo pip install pycrypto" でやると成功するので、そこでインストールした先である "/usr/local/lib64/python2.7/site-packages/" 以下から持ってくることで解決しました。
あと、Lambdaモジュールが利用するNetworkInterfaceの存在するsubnet内に例えIGW向けのルートが張ってあってもインターネットに出ることができません*3。 言い換えると、全てのAWS APIが使えなくなります。
これを避けるためにManaged NATを立てるか、S3だけであればS3 Endpointへのルーティングが必要となります。
やってみた
2回目以降エラーログが出力されたりする問題はありますが、とりあえずAWS LambdaのTestでssh接続を行って、リモートのコマンドを実行することには成功しました。
これは中々出来ることが広がるアップデートだと思います。 この使用方法が想定されていたかどうかは知りませんが。
ソースコード
簡単なサンプルを以下のpublicプロジェクトにアップロードしました。
ご自由にご参照/ご利用下さい。