suguru.dev

バンクーバーで働くエンジニアの備忘録

Node.js + Github Private Registry

概要

Github Private Registryにより、プライベートなモジュールを簡単に作成することができるようになりました。 今回はGithub Actionsを用いてNPMモジュールをGithub Private registryに登録・利用する方法を紹介します。

モジュール化したいリポジトリの設定

Github Actionの設定

まず、ドキュメントに沿って以下のようなYamlファイルを作成します。

# .github/workflows/release.yml
name: Release
on:
  release:
    types: [created]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    # Setup .npmrc file to publish to npm
    - uses: actions/setup-node@v1
      with:
        node-version: '14.x'
        registry-url: 'https://npm.pkg.github.com'
    - run: yarn
    - run: yarn publish
      env:
        NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Github Actionsではリポジトリに紐付いたGITHUB_TOKENが自動的に挿入されます。このトークンはこのGithub Private Registryに対する書き込み権限を持っているので、そのまま使用することができます。

package.jsonの設定

Github Registryを使用する際に、パッケージ名に組織名を入れる必要があります。

{
  "name": "@<org name>/<package name>"
}

後はNPMのお作法に従って、

npm version patch

でタグを作成し、Github上でリリースを作成すると自動的にPrivate RegistryにPublishしてくれます。リリースはこちらからです。f:id:suguru03:20201017090921p:plain

モジュールを使用したいリポジトリの設定

次にモジュールを使用したいリポジトリ側でインストールできるように設定する必要があります。

.yarnrcの作成

使用したいリポジトリのルート配下に以下のような.yarnrcを作成します。以下の設定で特定のモジュールのみをGithub Registryから取得することができます。

registry "https://registry.npmjs.org"
"@<org name>:registry" "https://npm.pkg.github.com"

ローカルの設定

ローカル開発で使用する場合、Github Registryにアクセスできるようにする必要があります。

Personal access tokensのページからアクセストークンを作成します。権限は read:packagesだけで十分です。 f:id:suguru03:20201017092134p:plain

作成したトークンを利用してnpm loginします。

npm login --registry=https://npm.pkg.github.com
Username: suguru03
Password: <access token>

ログインが完了すると~/.npmrcが更新されており、先程のトークンがあることが確認できます。(確認できてしまっていいのかどうか…)

//registry.npmjs.org/:_authToken=<access token>

ここのトークンを書き換える必要があるだけで、npm loginは必須ではないです。

以上の設定で、リポジトリ配下でyarn installすることができるようになります。

CIの設定

Github ActionsでもCirlceCIでも、ローカル開発と同様にアクセストークンを作成する必要があります。

Github Actionsの場合

Github Actionsで自動的に挿入されるGITHUB_TOKENは他のGithub Private Registryにアクセスする権限がないため、別途作成する必要があります。 またGTIHUB_TOKENは上書きできないため、作成したトークンはNPM_TOKENとしました。NPM_TOKENはOrganization配下のsecretsに入れると、再利用ができて便利かと思います。

以下はstepsの一部抜粋です。

steps:
  - uses: actions/checkout@v2
  - uses: actions/setup-node@v1
    with:
      node-version: '14.x'
      registry-url: 'https://npm.pkg.github.com'
  - name: yarn install
    run: yarn
    env:
      NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

CircleCIの場合

CircleCIでは以下のように書きました。

steps:
  - run:
      name: npm login
      command: echo "//npm.pkg.github.com/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
  - run:
      name: yarn install 
      command: yarn

workflows:
  deploy-workflow:
    jobs:
      - build:
          context: node

こちらもGithub Actionsと同様にcontextを使用して他のリポジトリでも使用できるようにしておくと便利かと思います。

また、公式npm loginを叩いていますが、実際は叩かずともインストールが可能です。

dependencies:
  pre:
    - echo -e "$NPM_USER\n$NPM_PASS\n$NPM_EMAIL" | npm login

まとめ

Node.jsでのGithub Private Registryの使い方について紹介しました。 NPM Private Registryは少しハードルがありましたが、Github Private Registryは無料枠もあり気軽に試せるのでとてもオススメです、是非試してみてください。

リンク

AST + Prettierで快適にコードを一括変換しよう

概要

この記事はQiita Node.js Advent Calendar 2019 5日の記事です。

「Node.jsのプロジェクトにTypeScriptを導入したい」

ちょうど去年の12月に面接で聞かれたことでした。

あなたならどう答えますか?

今回は面接のネタとしても使える、ASTとPrettierを利用してコードを一括変換する方法を紹介します。

ASTとは

AST(Abstract Syntax Tree)とは抽象構文木のことで、コードを抽象化しツリー構造にパースしたものです。 身近なところではTypeScript、Eslint、Prettierなどで使用されています。

コードの変換方法

コードを変換するアプローチは色々ありますが、ASTに出会う前はよく正規表現を利用してコードの変換をしていました。

正規表現による変換

例えば varconst に変換するくらいなら正規表現のほうが早いかもしれません。

例1

以下の例を見てみましょう。

// test.js
var a = 'a';
var b = 'b'; var c = 'c';

これくらいは余裕ですね。

// 回答例
const str = fs.readFileSync(path.resolve(__dirname, './test.js'), 'utf8');
str.replace(/var/g, 'const')

例2

次の例はだいぶ意地悪なコードですが、まだいけそうな気がします。

var d = 'd'; var variable = 'var var var';
// 回答例
str.replace(/var\s(.+?=)/g, 'const $1')

例3

次の例はいかがでしょう。

/**
 Goの型宣言
 var e =1;
 C#の型宣言
 var e = 1;
**/
var e = 1;

まだなんとかなりそうですが…

正規表現でコードを変換する場合、試行錯誤して例外を見つけ、最終的に呪文のような正規表現を書くことになるかと思います。

ASTによる変換

そこで登場するのがASTです。

今回はbabelのパーサを使いコードをASTに変換します。 以下のように実行することでASTを出力することができます。ちなみに util.inspectを使うことでネスト構造のオブジェクトを展開することができます。

const parser = require('@babel/parser');
console.log(util.inspect(parser.parse(str), false, null));

以下は実行結果よりvar宣言の部分とコメントの部分の抜粋です。

// var宣言部分
Node {
  type: 'VariableDeclaration',
  kind: 'var',
  ...
}

// コメント部分
trailingComments: [
  {
    type: 'CommentBlock',
    value: '*\n Goの型宣言\n var e =1;\n C#の型宣言\n var e = 1;\n*',
    ...
  }
]

AST上では var の宣言とコメントは全く別物として扱われます。また const 定義の部分は以下のような定義になります。

// const宣言部分
Node {
  type: 'VariableDeclaration',
  kind: 'const',
  ...
}

varconstを比較したとき、大きな違いは kind の部分だけになります。

どうですか、AST簡単ですね!

しかし大きな問題があります。 パーサはパースしかしてくれずコードの再形成はしてくれないのです…困った。

Prettierによるコードの再形成

そこでPrettierの出番です。

PrettierJavaScript界隈では最も有名なコードの整形ツールの一つです。具体的にはコードを特定のパーサでASTに変換し、そのASTを元にコードを整えつつ再形成します。JavaScriptの場合、先程登場した @babel/parser が使用されています。

今回やりたいことはシンプルで、パース後のASTの var の部分を const に変換し、Prettierにコードの再形成をしてもらいたいのです。

そのために作ったツールがprettier-hookです。このツールは上記の機能をサポートしています。

コードは以下のように書きます。

// hook.js
const { Ast, hooks } = require('prettier-hook');

function parse(ast) {
  // ASTの変換処理
  return ast;
}
hooks.babylon.addHook(parse);

余談ですが、babylon とはbabel がモノレポ化前の名残で、現在は@babel/parserとして開発されています。

このparse関数の中に任意のトラバーサを挟むことができます。 今回はvarconstに変換したいだけなので、コードは以下のように書けます。

function parse(ast) {
  new Ast()
    // 変換したいTypeを指定
    .set('VariableDeclaration', (parent, key) => {
      const node = parent[key];
      node.kind = 'const'; // varをconstに変換する
    })
    .resolveAst(ast);
  return ast;
}

このAstのクラスがprettier-hookのトラバーサになります。 変換したいNodetypeを指定すると、そのtypeが現れたときに関数が呼び出され、ASTの変換を可能にしています。

以下のように実行することで、ASTを使用して変換することができます。

npx prettier-hook --require hook.js test.js
// or
npx prettier-hook --require hook.js --write test.js
// 実行結果
const a = "a";
const b = "b";
const c = "c";
const d = "d";
const variable = "var var var";
/**
 Goの型宣言
 var e =1;
 C#の型宣言
 var e = 1;
**/
const e = 1;

ツールの紹介

prettier-hookを利用して作成したツールを軽く紹介します。

JavaScrip2TypeScript

JavaScriptファイルをTypeScriptファイルに変換するツールです。

主な機能は、

などがあります。

JSDocの例

/**
 * @param {number} a
 * @param {boolean} [b]
 * @param {string} [c]
 **/
function test(a, b, c = 1) {
  return 1;
}
/**
 * @param {number} a
 * @param {boolean} [b]
 * @param {string} [c]
 **/
function test(a: number, b?: boolean, c: string | number = 1) {
  return 1;
}

Classの例

class Test {
  constructor() {
    this.num = 1;
    this.str = 'str';
  }
  getNum() {
    return this.num;
  }
  getStr() {
    return this.str;
  }
}

Test.num = 1;
class Test {
  static num: number;
  num: number;
  str: string;
  constructor() {
    this.num = 1;
    this.str = "str";
  }
  getNum() {
    return this.num;
  }
  getStr() {
    return this.str;
  }
}

Test.num = 1;

ツール作成も含め、1週間弱で400ファイル以上あるプロジェクトをTypeScriptに変換できました。

実際、ツールも完璧ではないのと、アンチパターンのコードが多かったので、変換後に手直しが必要でした。

まとめ

コードを変換する際にASTを利用するアプローチについて紹介しました。 正規表現でコードを変換するコードを書くより、ASTで書くことにより保守性も上がるケースも多いのでは無いかと思います。 ぜひコードを変換する際にはASTを使ってみて、面接でネタにしてくださいね!

リンク

Unity Editor上でのVersion Controlの使い方 〜Perforce編〜

概要

UnityにはBuilt-inのVersionControlという関数があり、これを利用することでシェルコマンドを叩くこと無くGitやPerforceなどにCommit・Submitすることができます。

Unity Editor上からファイルを自動生成・削除、及びPerfoceに反映させるツールを作ったのでその一部を紹介します。

Version Controlの設定

[Project Setitngs] > [Editor]上にVersion Controlという項目を設定します。今回はPerfoceを使用するためPerfoceの設定をしました。

f:id:suguru03:20190623052840p:plain

Connectのボタンを押してConnectedになっていることを確認してください。

VersionControlクラスの使い方

まずUnityEditor.VersionControlをインポートします、C#ではusingを使用します。

using UnityEditor.VersionControl;

また各関数でassetsのパスが必要となるため、assetsを用意しておきます。

var assetPath = Application.dataPath;
var assets = new AssetList
{
    new Asset(assetPath)
};

Task

今回使用する関数はどれもUnityEditor.VersionControl.Taskを返します。必要に応じでWaitを呼んでください。 このTaskはawaitableではないため同期処理になりプロセスを専有します。他の作業がブロックされるのでなるべく呼ぶ回数を減らすことをおすすめします。

Checkout

Perfoceではすべてのファイルがreadonlyなので、ファイルを変更する前に必ずcheckoutする必要があります。 そのため必ずWaitを呼ぶ必要があります。

Provider.Checkout(assets, CheckoutMode.Asset).Wait();

PerfoceにはChaneglistという概念があります。 Gitのbranchに似てますが、同時に複数のChangelistを使用することができます。

例えば作業AとBを同時にする必要があり、それぞれを別のChangelistで管理することでSubmitするタイミングを調整することができます。

ここでCheckoutされたファイルは自動的にdefaultのChangelistに入ります。

Add

新しいファイルを追加します。ディレクトリパスを指定することでディレクトリ配下の新規ファイルを追加することができます。

Provider.Add(assets, true).Wait();

これらのファイルはAddコマンドを呼ばない限りPerfoce上で確認することができません。

これがとても厄介で、追加漏れしたファイルは存在しているにも関わらず、Perfoce上で確認ができないため困ることが多々あります。 この追加漏れをチェックするreconcileというコマンドがありますが、かなり不便です。

Delete

既存のファイルを削除します。

Provider.Delete(assets).Wait();

Revert

変更を破棄します。 Perforceの場合、チェックアウトされたすべてのファイルがSubmitの対象になります。そのため変更されてないファイルはUnchangedで破棄する必要があります。

Provider.Revert(assets, RevertMode.Normal).Wait();
Provider.Revert(assets, RevertMode.Unchanged).Wait(); // 未変更のファイルのみを破棄

Submit

変更を反映します。 changeSetのdescriptionはChangelistのdescriptionにリンクします。第四引数はSaveOnlyです。

var changeSet = GetChangeSet();
Provider.Submit(changeSet, assets, changeSet.description, true).Wait();

Submitを2回呼ぶとコネクションが切れることがあるので、2回呼ぶことは避けたほうが良さそうです。 コネクションが切れてしまった場合はProject Settingsからまた有効にする必要があります。

Example

実際のコードではasset配下は範囲が広すぎてパフォーマンス上の懸念があるため、作業範囲を限定します。 この例は、Zipファイルを展開し、Zip内に存在するファイルの追加・変更と存在しないファイルの削除をResources配下に反映します。

// Zipファイルの展開
using (var archive = new ZipArchive(stream, ZipArchiveMode.Read, true))
{
  var assetPath = Application.dataPath;
  var resourcePath = Path.Combine(assetPath, "Resources");

  var assets = new AssetList
  {
      new Asset(resourcePath)
  };

  // Resource配下をCheckout
  Provider.Checkout(assets, CheckoutMode.Asset).Wait();

  // Zipファイル内に存在するパスのキャッシュ
  var assetPathSet = new HashSet<string>();
  foreach (var entry in archive.Entries)
  {
      var destinationPath = Path.Combine(resourcePath, entry.FullName);
      var dirName = Path.GetDirectoryName(destinationPath);
      if (dirName != null)
      {
          // ディレクトリが存在しない場合エラーになるので作成する
          Directory.CreateDirectory(dirName);
      }
      entry.ExtractToFile(destinationPath, true);
      // フルパスではなくasset配下のパスが必要なためassetのパスを取得する。これはmetaファイルにも使用される。
      assetPathSet.Add(new Asset(destinationPath).assetPath);
  }

  // ディレクトリのmeta情報のために追加する
  assetPathSet.Add(new Asset(resourcePath).assetPath);

  // ディレクトリ配下の新規ファイルを追加する
  var addTask = Provider.Add(assets, true);
  addTask.Wait();

  // Zipファイルに存在しないファイルの削除
  var deleteAssets = new AssetList();
  // addTask.assetListはチェックアウトされているすべてのファイル名を取得できる
  deleteAssets.AddRange(addTask.assetList.Where(asset => !assetPathSet.Contains(asset.assetPath)));
  Provider.Delete(deleteAssets).Wait();

  // これらのファイルをデータ配下のChangelistに移動させる
  var changeSet = GetChangeSet("データ");
  Provider.Submit(changeSet, addTask.assetList, changeSet.description, true).Wait();

  // 未変更のファイルを取り消す
  Provider.Revert(assets, RevertMode.Unchanged).Wait();
}

まとめ

Unity EditorのVersionControlの使い方について紹介しました。 この順番で呼ばないとうまく動かなかったりハマりどころが多かったりしますが、自動化すると便利なことは多いのでぜひトライしてみてください。

Gitを使いたい。

リンク

FlatBuffers導入

概要

FlatBuffersを導入手順についての記事です。

FlatBufferとは

FlatBuffersとはGoogle社が開発しているクロスプラットフォーム対応のシリアライゼーションライブラリです。主にゲームで使われることを目的として開発されています。Protocol Bufferに似ていますが、大きな違いはバイナリデータをパースせずにシリアライズされたデータを使用するため、高速かつメモリを効率的に使うことができます。

Flatbuffersのインストール

はじめにflatbuffersbrewでインストールします。 brewが使えない場合はBuildingのページを参考にインストールすることができます。

brew install flatbuffers

flatcというコマンドが使えるようになれば準備完了です。

FBSファイルの作成

次にFBSファイルを作成します。FBSファイルとはFlatbuffers用のスキーマ定義ファイルです。これによりバイナリファイルの作成および各言語のparserを生成することができます。

Tutorialを参考にFBSファイルを作成します。

// monster.fbs

// Example IDL file for our monster's schema.
namespace MyGame.Sample;
enum Color:byte { Red = 0, Green, Blue = 2 }
union Equipment { Weapon } // Optionally add more tables.
struct Vec3 {
  x:float;
  y:float;
  z:float;
}
table Monster {
  pos:Vec3; // Struct.
  mana:short = 150;
  hp:short = 100;
  name:string;
  friendly:bool = false (deprecated);
  inventory:[ubyte];  // Vector of scalars.
  color:Color = Blue; // Enum.
  weapons:[Weapon];   // Vector of tables.
  equipped:Equipment; // Union.
  path:[Vec3];        // Vector of structs.
}
table Weapon {
  name:string;
  damage:short;
}
root_type Monster;

バイナリファイルの作成

バイナリファイルはJSONまたはJSON5のフォーマットを使用します。今回はJSON5を使用してバイナリファイルを生成します。

// monsterdata.json5

{
  pos: {
    x: 1,
    y: 2,
    z: 3
  },
  hp: 300,
  name: 'Orc'
}

先程生成したFBSファイルを使用してJSONデータをバイナリファイルに変換します。

flatc -b monster.fbs monsterdata.json5

monsterdata.binが生成されればOKです。

JavaScriptで読み込み

今回はJavaScriptファイルをFBSファイルより自動生成し、実際にNode.jsでバイナリファイルを読み込みます。

flatc --js monster.fbs

monster_generated.jsファイルが生成され、これを使用して実際にバイナリファイルを読み込んでみます。

Nodeの実行環境をTutorialを参考に作成し実行し、ログが出れば成功です。

yarn init -y
yarn add flatbuffers
// example.js

const fs = require('fs');
const path = require('path');
const { flatbuffers } = require('flatbuffers');

const { MyGame } = require('./monster_generated');

const filepath = path.resolve(__dirname, 'monsterdata.bin');
const data = new Uint8Array(fs.readFileSync(filepath));

const buf = new flatbuffers.ByteBuffer(data);
const monster = MyGame.Sample.Monster.getRootAsMonster(buf);
console.log(monster.hp()); // 300
console.log(monster.name()); // Orc
node example.js

まとめ

FlabBuffersの導入をしました。まだベンチマークを計測していないですが、C#クライアントではうまく動いてより良い感じです。またdefault valueを使用することでデータ量を減らすこともできるようなので、ぜひ使っていきたいと思います。

Link

Serverless + AWS Lambda + API Gatewayのヘッダマッピング

概要

AWS Lambda上でリクエストヘッダの取得とレスポンスヘッダの追加をServerless上で設定する方法がわからなくハマったので、それについてまとめました。

リクエストヘッダのマッピング

AWS Lambdaで生のリクエストヘッダを取得するためには、API Gateway上でマッピングテンプレートによりマッピングする必要があります。今回はいくつかのエンドポイントでヘッダ情報をチェックする必要があったため、こちらのドキュメントを参考に以下のように追記しました。

functions:
  hello:
    handler: header.hello
    events:
      - http:
          path: hello
          method: GET
          request: ${self:custom.request}
custom:
  request:
    template:
      application/json:
        method: $context.httpMethod,
        body : $input.json('$'),
        headers:
          #foreach($param in $input.params().header.keySet())
          $param: $util.escapeJavaScript($input.params().header.get($param))
          #if($foreach.hasNext),#end
          #end

これでヘッダ情報が取得できるようになります。

export const hello = async (event, context) => {
  cosnole.log(event.headers);
);

レスポンスヘッダのマッピング

今回、AWS Gatewayのデフォルトはapplication/jsonのようで、これをapplication/json; charset=utf-8に書き換える必要がありました。yaml上で解決したかったのですがうまく解決できず。今回はこちらを参考にコードに追記して解決しました。

export const hello = async (event, context) => ({
  statusCode: 200,
  headers: {
    'Content-Type': 'application/json; charset=utf-8',
  },
  body: JSON.stringify({
    message: 'Go Serverless v1.0! Your function executed successfully!',
    input: event,
  }),
});

共通関数の作成

以下の目的のため、共通の関数を作成しました、ソースコードこちらです。

  • 冗長なコードを減らす
  • リクエストヘッダのチェック
  • レスポンスヘッダに上記の情報を追記する
  • Serverlessの関数はasync関数を取らないといけないが、もっと自由に定義したい
  • 型推論を利用したい

header.ts -> src/funcitons/example.tsに移動し、serverless.yamlの記述を書き換えます。

functions:
  hello:
    handler: src/functions/example.hello
    events:
      - http:
          path: hello
          method: GET

ディレクトリ構造は以下のような感じです。

|--package.json
|--serverless.yml
|--src
|  |--errors.ts
|  |--functions
|  |  |--example.ts
|  |  |--util.ts
|--tsconfig.json
|--yarn.lock

次にaws-lambdaの型定義を追加します。

$ yarn add -D @types/aws-lambda

共通関数は以下のように書きました。

import { Handler, Context, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

import { CustomError } from '../errors';

interface HandlerResult {
  statusCode?: number; // defaultは200
  body?: string | object; // JSON.stringifyするのでなんでもOK
}

type CustomHandler = (
  event: APIGatewayProxyEvent,
  context: Context,
) => void | HandlerResult | Promise<HandlerResult>;

export const createAPIGatewayHandler = (handler: CustomHandler): Handler => async (
  event: APIGatewayProxyEvent,
  context: Context,
): Promise<APIGatewayProxyResult> => {
  try {
    // check headers

    const { statusCode = 200, body = '' } = (await handler(event, context)) || {};
    return createResponse({
      statusCode,
      body: JSON.stringify(body), // JSON.stringify(undefined)にならないようにbodyを初期化
    });
  } catch (error) {
    let { statusCode, message } = error;
    if (!(error instanceof CustomError)) {
      // unknownエラーは500エラー
      statusCode = 500;
      message = 'Internal Server Error';
    }
    return createResponse({
      statusCode,
      body: JSON.stringify({ message }),
    });
  }
};

const createResponse = (response: APIGatewayProxyResult): APIGatewayProxyResult => ({
  ...response,
  headers: {
    'Content-Type': 'application/json; charset=utf-8',
    ...response.headers,
  },
});

example.tsは以下のようになり、event, contextともに型推論が利くので型定義は不要です。

import { createAPIGatewayHandler } from './util';

export const hello = createAPIGatewayHandler(event => ({
  body: {
    message: 'Go Serverless v1.0! Your function executed successfully!',
    input: event,
  },
}));

まとめ

Lambda上でリクエストヘッダ情報のチェック・レスポンスヘッダの書き換えができるようになりました。認証のためのCustom Authorizersという機能もあるのでこちらもまたチェックしてみようと思います。

リンク

Serverless + TypeScript + AWS Lambdaの開発環境構築

概要

Serverless FrameworkとはLambda関数などを開発・デプロイするためのツールです。今回はServerless + TypeScript + AWS Lamdbaのローカル開発環境の導入手順について書きます。

プロジェクトの作成

はじめにServerlessの環境構築をします。今回はAWS LamdbaのNode.jsを利用するため、templateはaws-nodejsで作成します。pathのサービス名は適宜変更してください。

$ npx serverless create --template aws-nodejs --path my-service
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.36.0
 -------'

ドキュメントにはnpm install -g serverlessと書かれていますが、npxを使用することをオススメします。

このときのserverlessのバージョンはv1.36.0でした。

次にnpm packageを作成します。Serverlessのライブラリは必須ではないですが、CLIのバージョン固定するためにインストールしておきます。

$ cd my-service

$ yarn init
$ yarn add -D serverless

ローカル開発環境の構築

次にserverless-offlineのライブラリでローカル環境の構築を行います。

$ yarn add -D serverless-offline

serverless.ymlserverless-offlineとテスト用にhelloを以下のように追記しておきます。

plugins:
  - serverless-offline

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: GET

起動して以下のログが出れば成功です。

$ yarn sls offline start
Serverless: Offline listening on http://localhost:3000

package.jsonスクリプトを追加しておくと便利ですね。

curlで叩いてみます。

$ curl http://localhost:3000/hello
{"message":"Go Serverless v1.0! Your function executed successfully!",... }

上記のログが出れば設定完了です。

デプロイ

次にAWS Lambdaへデプロイします。serverless.ymlに以下を追記します。region、profile名を適宜変更してください。

provider:
  name: aws
  runtime: nodejs8.10
  region: us-west-2
  profile: my-service

AWSの認証キーは必要に応じて~/.aws/credentialsに追記します。

[my-service]
aws_access_key_id = <your-key-here>
aws_secret_access_key = <your-secret-key-here>

以下のコマンドでデプロイします。

$ yarn sls deploy

AWSコンソール上に表示されていれば完了です。

TypeScriptの設定

次にTypeScriptの設定を行います。設定はとてもシンプルです。

$ yarn add -D typescript serverless-plugin-typescript

offlineと同様に以下の記述を追記します。

plugins:
  - serverless-offline
  - serverless-plugin-typescript

handler.jshandler.tsに変更し、以下のように書き換え、ローカルサーバが正常に立ち上がれば完了です。

export const hello = async (event, context) => ({
  statusCode: 200,
  body: JSON.stringify({
    message: 'Go Serverless v1.0! Your function executed successfully!',
    input: event,
  }),
});

ソースコードこちらに上げてあります。

まとめ

まだ使い始めたばかりですが、Serverless FrameworkでAWS Lambdaの開発・テストがローカル環境で行うことができとても便利です、ぜひ試してみてください。

リンク

MetabaseをElastic Beanstalkにデプロイする

概要

Metabaseを導入したので、それの手順についてまとめます。

Metabaseのデプロイ

こちらのリンクからAWSにデプロイするボタンがあるのですが、うまく動かなかったため今回は手動でセットアップします。

まずはじめにElastic Beanstalkのサーバを構築します。プラットフォームは事前設定済みプラットフォームのDockerを選択します。

f:id:suguru03:20181205161604p:plain

次にアプリケーションコードをアップロードします。ソースコードは既にS3上に上がっているためS3のURLを指定します。

https://s3.amazonaws.com/downloads.metabase.com/v0.31.1/metabase-aws-eb.zip

最新バージョンはこちらで確認できます、適宜URLのバージョン部分を書き換えてください。

f:id:suguru03:20181205162405p:plain

f:id:suguru03:20181205165545p:plain

リージョンのエラーが出る場合はソースコードをローカルにダウンロードし、ローカルファイルからダウンロードしたソースコードを選択しアップロードします。 「更にオプションを追加」をクリックし適宜設定し、環境を構築します。

Auroraの設定

今回は既存のAurora上にMetabase用のDBを作成します。新しくDBを作成する場合はこちらを参考にしてください。

ルートユーザでログインし、Aurora上にMetabase用のデータベースとユーザを作成します。

CREATE DATABASE metabase CHARACTER SET utf8mb4;
CREATE USER 'metabase.admin'@'%' IDENTIFIED BY 'password';
GRANT SELECT ON *.* TO 'metabase.admin'@'%' WITH GRANT OPTION;

必要に応じて、対象となるデータベース上にReadonlyユーザを作成してください。

環境変数の設定

スタートガイドに従ってElastic Beanstalk上に環境変数を設定します。上記で生成したユーザを設定してください。

f:id:suguru03:20190105093442p:plain

設定を更新して以下の画面が表示されれば準備完了です。

f:id:suguru03:20190106072554p:plain

まとめ

Metabaseとても便利そうなのでぜひ使ってみてください。

ソース