プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

Syntaxhighlighter V4 に YAML ブラシを追加する

こちらの記事を書いたとき、 Syntaxhighlighter V4 に YAML のブラシがないことに気づきました。 なので、YAMLのブラシを追加できないか調べてみた顛末をまとめておきます。

なお、はてなブログで Syntaxhighlighter v4 を使う話は、以前下記にまとめてますので、ご参考までに。

blog.mori-soft.com

YAML ブラシ

SyntaxHgihlighter に Brushes and Themes というページがあり、そこに公式・非公式のブラシ一覧が載ってます。 ここを見ると、非公式としてですが YAML のブラシがあるようです。

GitHub - ErikWegner/brush-yaml

ということで、まずは、これを使ってみようと思います。

YAML ブラシのビルド

YAML ブラシのページを見ると SyntaxHighlighter のビルドの方法に従って、ビルドしてくれとあります。

Building · syntaxhighlighter/syntaxhighlighter Wiki · GitHub

なので、以前 SyntaxHighlighter V4 をビルドしたのと同じように試してみます。

mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d$ git clone https://github.com/ErikWegner/brush-yaml.git
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d$ cd syntaxhighlighter
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$ ./node_modules/gulp/bin/gulp.js build --brushes=../brush-yaml/brush.js --theme=default
[14:32:21] Failed to load external module @babel/register
[14:32:21] Requiring external module babel-register
[14:32:21] Using gulpfile ~/work/syntaxhighlighter.d/syntaxhighlighter/gulpfile.babel.js
[14:32:21] Starting 'build'...
[14:32:21] Unknown brush "../brush-yaml/brush.js".
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$

うまくいきません。 ちなみに --build=all の場合は問題なくビルドできます。

指定方法を変えてみます。絶対パス

mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$ ./node_modules/gulp/bin/gulp.js build --brushes=/home/mor/work/syntaxhighlighter.d/brush-yaml/brush.js --theme=default
[14:34:07] Failed to load external module @babel/register
[14:34:07] Requiring external module babel-register
[14:34:08] Using gulpfile ~/work/syntaxhighlighter.d/syntaxhighlighter/gulpfile.babel.js
[14:34:08] Starting 'build'...
[14:34:08] Unknown brush "/home/mor/work/syntaxhighlighter.d/brush-yaml/brush.js".
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$

うまくいきません。 公式のブラシは repos ディレクトリ以下にあるようなので、リンクを貼って

mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$ cd repos/
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter/repos$ ln -s ../../brush-yaml .
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter/repos$ cd ..
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$ ./node_modules/gulp/bin/gulp.js build --brushes=repos/brush-yaml/brush.js --theme=default
[14:36:18] Failed to load external module @babel/register
[14:36:18] Requiring external module babel-register
[14:36:19] Using gulpfile ~/work/syntaxhighlighter.d/syntaxhighlighter/gulpfile.babel.js
[14:36:19] Starting 'build'...
[14:36:19] Unknown brush "repos/brush-yaml/brush.js".
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$

やっぱりうまくいきません。

docker で syntaxhighlighter V4 をコンパイル

困ったなと思っていたら、ブラシの生成一式を docker にまとめてくれている方がいらっしゃいました。

github.com

以前の記事を書いた際に参考にした issues に docker のことを追記してくれてました。)

Building: loadReposFromCache(...).error is not a function · Issue #428 · syntaxhighlighter/syntaxhighlighter · GitHub

なので、これを試してみます。

[~/tmp] # docker pull crazymax/syntaxhighlighter
[~/tmp] # mkdir syntax
[~/tmp] # docker run -it --rm -v "$(pwd)/syntax:/syntaxhighlighter/dist" crazymax/syntaxhighlighter:latest

~/tmp/syntax というのがローカル側のディレクトリで、コンテナの /syntaxhighlighter/dist にマウントしています。

で、実行させると、

[~/tmp] # ls -l syntax/
total 880
-rw-r--r-- 1 admin administrators  94034 2018-08-24 12:10 index.html
-rw-r--r-- 1 admin administrators 285507 2018-08-24 12:10 syntaxhighlighter.js
-rw-r--r-- 1 admin administrators 407571 2018-08-24 12:10 syntaxhighlighter.js.map
-rw-r--r-- 1 admin administrators   9094 2018-08-24 12:09 theme-default.css
-rw-r--r-- 1 admin administrators   9264 2018-08-24 12:09 theme-django.css
-rw-r--r-- 1 admin administrators   9474 2018-08-24 12:09 theme-eclipse.css
-rw-r--r-- 1 admin administrators   9120 2018-08-24 12:09 theme-emacs.css
-rw-r--r-- 1 admin administrators   9197 2018-08-24 12:09 theme-fadetogrey.css
-rw-r--r-- 1 admin administrators   9118 2018-08-24 12:10 theme-mdultra.css
-rw-r--r-- 1 admin administrators   9141 2018-08-24 12:10 theme-midnight.css
-rw-r--r-- 1 admin administrators   9135 2018-08-24 12:10 theme-rdark.css
-rw-r--r-- 1 admin administrators  10151 2018-08-24 12:10 theme-swift.css
[~/tmp] # 

のように、syntaxhighlighter のファイルが生成されています。docker実行中の画面を見ると、yamlのブラシも指定されているのが分かります。

[03:10:40] Starting 'build'...
[03:10:50] Brushes: applescript, as3, base, bash, coldfusion, cpp, csharp, css, delphi, diff, erlang, groovy, haxe, java, javafx, javascript, perl, php, plain, powershell, python, ruby, sass, scala, sql, swift, tap, typescript, vb, xml, repos/brush-halcon/brush.js, repos/brush-IEC61131/brush.js, repos/brush-kotlin/brush.js, repos/brush-latex/brush.js, repos/brush-Makefile/brush.js, repos/brush-mel/brush.js, repos/brush-objective-c/brush.js, repos/brush-yaml/brush.js
[03:10:50] Hash: 6ed82d7c6ef7b999f01e

なんで自分で試したときはうまくいかなかったのだろうか?

はてなブログに反映

作成した js ファイルを差し替えます。 はてな側では syntaxhighlighter の指定で

<pre class="brush: yaml">

と書くと、

f:id:junichim:20180824145706p:plain

のように無事に、YAMLもハイライト表示されるようになりました。

Swagger による API Gateway の定義

API Gateway を使う際、だんだんAPIの数が増えてくる(リソース・メソッドが増えてくる)と、コンソールで定義するのが大変になってきます。 特に、APIのリネームなんてことが起こったら、嫌になってきます。

なので、それを避けるために、API定義を Swagger で記述して、それを API Gateway にインポートさせて API 定義を簡単に行えるようにしています。

※ Swagger とは、APIを記述するための仕様で、YAMLやJSONでAPIを記述することができます。アプリケーションのドキュメントからAPIを定義したり、定義したAPIからコードを生成したり、反対に実装から、API定義を起こすこともできるそうです。今回はドキュメント(とはいっても自分でざっくり考えたもの)からAPI定義を記述するための Swagger Editor を使いました。 Swagger そのものは解説記事がたくさんあるので、そちらをご覧ください。これとか分かり易かったかな?

qiita.com

しかし、 AWS 特有の設定等は、標準の Swagger では記述できないため、このままではいちいち手作業で設定する必要が残ります。 そこで、API Gateway の Swagger 拡張を使って、AWS特有の設定も Swagger ファイルで定義してしまおう、という作業を行いました。

ということで、毎度の作業メモです。以下では、記述したい AWS の機能ごとにまとめてあります。

Cognito ユーザープールによるオーソライザーの設定方法

下記記事に Cognito ユーザープールによるオーソライザーを使う場合の Swagger 拡張の書き方がずばり載ってます。

REST API と Amazon Cognito ユーザープールを統合する - Amazon API Gateway

security definitions を定義して、各APIのメソッドの定義内の security で security definitions で定義した名前を参照すればOKです。 YAML だとこんな感じになります。

securityDefinitions:
  jm_test:
    type: apiKey
    name: Authorization
    in: header
    x-amazon-apigateway-authtype: cognito_user_pools
    x-amazon-apigateway-authorizer:
      providerARNs:
        - >-
          arn:aws:cognito-idp:ap-northeast-1:xxxxxxxxxxxx:userpool/ap-northeast-1_yyyyyyyyyyyyyyy
      type: cognito_user_pools
      security:
        - jm_test: []

Lambda 関数

一番分かり易そうなのが、これかな。プロキシリソースを対象に、API Gateway で Lambdaプロキシ統合をする場合の Swagger 定義の例になります。

サンプルだけなら、これもいいかも。

必要なことは、 x-amazon-apigateway-integration を定義することになります。 uri に Lambda 関数のARNをベースにサンプルのように記述します。 この際、httpMethod は必ず POST にします(こちらのリファレンスのページの httpMethod に載ってますのでご確認ください)。

YAMLだとこんな感じになりました。

      x-amazon-apigateway-integration:
        uri: >-
          arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/(Lambda関数のARN)/invocations
        responses:
          default:
            statusCode: '200'
        passthroughBehavior: when_no_match
        httpMethod: POST
        contentHandling: CONVERT_TO_TEXT
        type: aws_proxy

なお、uri のフォーマットの説明はこれだ、というピンポイントのものがないのですが、

の2つのリファレンスの内容から、上記のような形式だと推測されます(pathの後ろのスラッシュ以降が Lambda 関数呼び出しAPIになってるようです)。 また、上記の例ではLambdaのバージョンは指定していないので、 Qualifier はつけていません。

CORS の設定

CORS も設定しておきましょう。こちらのサンプルがよくわかります。

API Gateway のインポート API を使用して、リソースで CORS を有効にする - Amazon API Gateway

まず、 CORS を有効としたいリソースに対して、 OPTIONS メソッドを定義します。 OPTIONS メソッドでは Mock 統合を指定します。 次に、 responses と x-amazon-apigateway-integration に 返すヘッダーの定義を追加します。サンプルの通りですね。

YAML だとこんな感じでした。

OPTIONS メソッドの定義

    options:
      tags:
        - CORS
      summary: CORS support
      description: Enable CORS by returning correct headers
      consumes:
        - application/json
      produces:
        - application/json
      responses:
        '200':
          description: 200 response
          schema:
            $ref: '#/definitions/Empty'
          headers:
            Access-Control-Allow-Origin:
              type: string
            Access-Control-Allow-Methods:
              type: string
            Access-Control-Allow-Headers:
              type: string
      x-amazon-apigateway-integration:
        responses:
          default:
            statusCode: '200'
            responseParameters:
              method.response.header.Access-Control-Allow-Methods: '''GET,OPTIONS'''
              method.response.header.Access-Control-Allow-Headers: >-
                'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'
              method.response.header.Access-Control-Allow-Origin: '''*'''
        passthroughBehavior: when_no_match
        requestTemplates:
          application/json: '{"statusCode": 200}'
        type: mock

x-amazon-apigateway-integration の method.response.header.Access-Control-Allow-Methods は対象とするメソッド名を記述していますが、 '*' でも問題ないかと思います。

APIのレスポンスにヘッダ宣言を含めます。

  /api/sample/test:
    get:
      tags:
        - util
      summary: サンプル
      description: ''
      responses:
        '200':
          description: 成功時のレスポンス
          schema:
            $ref: '#/definitions/SuccessResponse'
          headers:
            Access-Control-Allow-Origin:
              type: string

x-amazon-apigateway-integration のレスポンスに Access-Control-Allow-Origin ヘッダに静的な値を設定します。

      x-amazon-apigateway-integration:
        uri: >-
          arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/(Lambda関数のARN)/invocations
        responses:
          default:
            statusCode: '200'
            responseParameters:
              method.response.header.Access-Control-Allow-Origin: '''*'''
        passthroughBehavior: when_no_match
        httpMethod: POST
        contentHandling: CONVERT_TO_TEXT
        type: aws_proxy

Lambda 関数を呼び出す権限の追加

以上を追加した Swagger ファイルを使って、API Gateway でインポートすると、無事に API が定義されます。一度 Swagger ファイルを作ってやれば何度でもインポートできるのでこれは便利ですね。

では、ということで、ステージにデプロイして、APIを呼び出すと、Lambda関数が呼び出せません。 なんでだろうか?と思い調べると、API Gateway に Lambda 関数を呼び出す権限が与えられていないから、ということでした。

いわれてみれば API Gateway のコンソールから Lambda 関数の呼び出しを設定する場合は、

f:id:junichim:20180824124228p:plain

のように、API Gateway に Lambda 関数を呼び出す権限を与えてもいいか?という確認画面がでて、これでOKを押すことで、Lambda関数を実行することができるとのことです(そんな画面あったなー、という感じですね)。

ということで、Lambda 関数の呼び出し権限を与える設定も行います。

今回は、Lambda 関数を呼び出す権限を持ったロールを定義し、これを Swagger で指定するという方法を取りたいと思います(ロールベースの権限設定)。

なお、API Gateway に必要な権限については、下記に詳しいと思います。

IAM アクセス許可により API へのアクセスを制御する - Amazon API Gateway

ロールの作成

API Gateway が Lambda 関数を呼び出す権限を持ったロールを作成したいと思います(実際の権限は下記チュートリアルに詳しいです)。

チュートリアル: 2 つの AWS のサービス統合と 1 つの Lambda 非プロキシ統合を使用して Calc REST API を作成する - Amazon API Gateway

今回は、AWSのコンソールを使って作成したいと思います。

コンソールから、IAMを選択し、『ロールの作成』を選択します。

f:id:junichim:20180824124924p:plain

次に表示された画面で、『AWSサービス』を選択し

f:id:junichim:20180824125100p:plain

下のサービス一覧のリストから『API Gateway』を選択します。

f:id:junichim:20180824125221p:plain

このとき、画面下部の『ユースケースの選択』が選ばれた状態になっています。 『次のステップ』ボタンを押して、次の場面に進むと、ポリシーを設定できる画面になります。

f:id:junichim:20180824125401p:plain

が、ここでは、先ほどの『ユースケース』に対応するものしか選べないようで自由に選択することができませんでした。 なので、一旦このまま先に進みます。

f:id:junichim:20180824125631p:plain

適当なロール名を入力して、『ロールの作成』ボタンを押せば、ロールが作成されます。

次に作成されたロールを選択すると概要が表示されます。

f:id:junichim:20180824125847p:plain

この画面で『ポリシーをアタッチします』を選び、Lambda関数呼び出し権限を追加します。 フィルタを使って、Lambda関数呼び出しの権限『AWSLambdaRole』を探して、チェックをつけます。

f:id:junichim:20180824130047p:plain

『ポリシーのアタッチ』ボタンを押せば、ロールにLambda関数呼び出し権限が追加されます。

ロールの指定

ロールの指定方法は、

x-amazon-apigateway-integration オブジェクト - Amazon API Gateway

に記載されているように credential にロールのARNを指定すればよいようです。

YAML だとこんな感じになりました。

      x-amazon-apigateway-integration:
        credentials: 'arn:aws:iam::xxxxxxxxxxxx:role/yyyyyyyyyyyyyyyyyyyy'
        uri: >-

x-amazon-apigateway-integration に対して追記するので、Lambda 関数を呼び出す全てのAPIに対して設定が必要になります。

この設定を追加後、再度 Swagger ファイルをインポートして、作成した API にアクセスすると問題なく Lambda 関数が呼べました。

(参考)別の権限追加方法

リファレンスを調べてみると API Gateway からLambda 関数の呼び出しは、Lambda 側に設定を持たせることもできるようです(リソースベースの権限設定)。

AWS Lambda アクセス許可 - AWS Lambda

こちらの方法のほうが、どのAWSサービスから Lambda 関数が呼ばれているかを把握できるので便利だ、というようなことが書いてありました。

今回はこちらの方法はとらなかったので、機会があれば試してみたいと思います。

たねあかし

Swagger 拡張による記述は大変便利ですが、上記の記述方法を調べていくのって大変ですよね? 実は、上記に述べた Swagger 拡張は一からすべて調べて書いたのではなく、一旦手作業で API Gateway を設定後、ステージの『エクスポート』にある『Swagger + API Gateway 拡張でエクスポート』を実行し、作成されたファイル(Swagger拡張を含むAPI定義)を元に必要な記述を調べていきました。

f:id:junichim:20180824132738p:plain

こうすれば、設定したい機能に必要な Swagger 拡張のキーワード等が分かるので、そこから詳しく調べていくことができます。

ここまで設定ができると API の追加・変更があった場合も API Gateway 側で手作業で設定することが(現時点では)ほぼなくなるので非常に簡単になりました。 Swagger 拡張、なかなか使えますね。

API Gateway の Lambda プロキシ統合について

blog.mori-soft.com

を書いた当時は、API Gateway のバックエンドとして Lambda を使う場合、Lambda 関数を定義して、統合リクエスト・統合レスポンスの設定をしていました。 でも、この少し後に、 Lambda プロキシ統合という便利な機能がリリースされていたことを知りました。

リリース時の記事がこちら。

Amazon API Gateway に API 設定を簡素化する 3 つの新機能を追加

(ずいぶん前ですが)この Lambda プロキシ統合を試したときに感じたことをメモしておきます。 ・・・今回は半分愚痴ですので、軽く流してください。

実現方法

自分では新しく調べたことは何もなくて、下記記事のとおり行ったら、すんなりとできました。

qiita.com

まとめていただいて、ありがたいです。

ドキュメントが今一つよくわからない

で、この記事で書きたかったのは、こちらのほうです。

結論からいうと、API Gateway の Lambda プロキシ統合の部分のドキュメントはちょっとわかりにくいかと思います。

まず、用語を整理しましょう。API Gateway で Lambda 関数を使う場合のことを Lambda 統合 と呼んでいるようです。 で、その Lambda 統合の中に

  • Lambda プロキシ統合
  • Lambda カスタム統合

があるという形のようです。こちらの記事に説明がありました。

docs.aws.amazon.com

Lambda カスタム統合というのは、既存の方法で、自前で統合リクエスト・統合レスポンスのマッピングを行うという方法です。

で、前述の新機能のリリース記事にもあるように、 API Gateway の便利な機能として

  • greedy (グリーディー)パス
  • ANY メソッド
  • Lambda プロキシ統合

の3つがあります。 このうち、greedyパスで定義されるAPIリソースのことを、『プロキシリソース』と呼んでいるようです。 こちらの記事でそのことが書かれています。

docs.aws.amazon.com

ここで、プロキシと名の付くのが2つ出てくるので非常にややこしい。 もっとも、ドキュメントをよく読めば、上記の3つの機能は独立した機能であり、個別に使うことが可能、とあるのですが、このことがなかなか伝わってきませんでした。

サンプル

あと、Lambdaプロキシ統合について書かれているサンプルが、プロキシリソースを対象にしたものばかりで、単にメソッドに対して Lambdaプロキシ統合を行いました、 というのがないようです。

後者のドキュメントをよくよめばいいんでしょうが、サンプル見るほうが手っ取り早かったりもしますしね。

ごく簡単な設定だし、四の五の言わず Lambdaプロキシ統合を試せば、十分、分かるんですけど、ドキュメントはもう少しなんとかしてほしいものです。

補足

Lambdaプロキシ統合時のCORSを有効にする方法について、ドキュメントではここで触れてます。

API Gateway リソースの CORS を有効にする - Amazon API Gateway

Lambdaプロキシ統合の場合、バックエンド(つまりLambda関数)内でレスポンスヘッダを指定してやる必要があるということを覚えておけば良さそうです。