5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Linc'wellAdvent Calendar 2019

Day 10

Serverless framework x CircleCI でAWS lambdaのローカル開発環境構築

Last updated at Posted at 2019-12-09

Linc’well Advent Calendar10日目の記事です。

当社が展開するクリニックグループの CLINIC FORの予約システム で、認証機能の一部にAWS Cognitoへの移行を進めております。

予約システム全体としてはモノリシックなRailsアプリケーションで開発をしてきたのですが、AWS CognitoをRailsアプリケーションの上に被すように実装する必要がり、連携をするためにAWS CognitoのTriggerをlambdaで実装する必要がでてきました。

この記事では、ローカル環境でのlambda開発、テストをどう構築したかをまとめます。

lambda 開発環境構築

今回は、Serverless Frameworkを使って開発を進めます。Frameworkのinstallとprojectを作成します。

npm install -g serverless
serverless create --template aws-nodejs --name cognito-triggers --path cognito-triggers
cd cognito-triggers

lambda の依存パッケージをインストール

RailsアプリケーションにAWS Cognitoを連携させるために、http clientとしてaxiosを使います。

npm init
npm install axios --save 

lambda のコードを書く

このは、AWS Cognitoのサインアップ前 Lambda triggerです。AWS Cognitoは、自前のバックエンド等と連携するため、沢山のTriggerを提供しています。

ご興味あるかたは、こちらをご覧ください。

サインアップ前に、入力されたemailが、登録済 or 未登録 か判定して、登録済であればCognitoへ登録、未登録であればCognitoへ登録しない仕様の実装です。

pre-sign-up.js
'use strict';
const axios = require('axios'); 
  
async function exists(server, data, callback) {
  
    let url = 'https://' + server + '/api/endpoint/exists';
    const config = {
        headers: {
            "Content-Type": "application/json"
        },
        responseType: 'json'
    };

    await axios.get(url, {params: data}, config)
        .then(response => {
            console.log('response:', response.data);
            callback(null, response.data);  
        })
        .catch(function (error) {       
            callback(error);
        });
}

module.exports.exec = async (event, context, callback) => { 
    console.log('start pre authentication user:', event.userName);
    console.log(event)

    let userAttributes = event['request']['userAttributes']
    let email = userAttributes["email"];

    let rails_server_url = process.env.API_HOST_NAME; 
    await exists(rails_server_url, {
        email: email
    }, (err, response) => {
        if (err) {
            return context.fail("Connection error");
        }

        // ユーザーの 登録済 or 未登録判定
        if (response.user_exists == false) {
            // 未登録
            console.log('user not exists:', email);
            context.succeed(event);         
        }
        else {
            // 登録済
            console.log('user exists:', email);
            context.fail("user exists");    
        }
    });
};

lambda をテストする

Lambdaをテストするために、下記2つをmockする必要あります。

  • AWS Cognitoのcontext
  • axios

これらをmockしてテストします。

テストに必要なパッケージをインストール

テストフレームワークとして jest、AWS Cognitoのmockとして aws-lambda-mock-context serverless-prune-pluginもインストールします。

npm install jest aws-lambda-mock-context serverless-prune-plugin --save-dev

event data の準備

加えて、AWS CognitoがTriggerで送信してくるeventも準備する必要あります。

pre-sign-up-event-data.js
module.exports = {
testEvent: {
    "userName": "test@example.com",
    "request": {
        "userAttributes": {
            "email": "test@example.com"
        }
    }
}
};

テストコード

pre-sign-up.test.js
'use strict'

const { exec } = require('./pre-sign-up'); 
const event = require('./pre-sign-up-event-data');

// mock
const context = require('aws-lambda-mock-context');
const axios = require('axios');
jest.mock('axios');

describe('user-migration.js.exec()', () => {
  describe('user_status: CONFIRM', () => {
      describe('user not exists', () => {
          it('return event', async () => {
              // axios mock
              axios.get.mockResolvedValue({
                  data: {user_exists: false}
              });
              const ctx = context();
              await exec(event.testEvent, ctx, function () {
              });

              // AWS Cognito の context判定
              await ctx.Promise
                  .then(result => {
                      expect(result).toBe(event.testEvent);
                  });
          });
      });

      describe('user exists', () => {
          it('through error', async () => {
              // axios mock
              axios.get.mockResolvedValue({
                  data: {user_exists: true}
              });
              const ctx = context();
              await exec(event.testEvent, ctx, function () {
              });

              // AWS Cognito の context判定
              await ctx.Promise
                  .catch(err => {
                      expect(err).not.toBeNull();
                  });
          });
      });
  });
});

テスト実行

ローカル環境でテスト実行できるはずです。

npx jest

lambda の deploy

dev, stg, prd と deploy をしたいと考えると、

sls deploy -v --stage dev

こんな感じでdeployできると嬉しい。

stage別の設定ファイルを作成する

  • environments/dev.yml
  • environments/stg.yml
  • environments/prd.yml

のように配置する。内容は下記の通り。

environments/dev.yml
profile: your_aws_profile_name
region: aws_region_name
functionNamePrefix: function-prefix-
apiHostName: your-host-name.co.jp

serverless.yml の編集

sls deploy -v --stage dev

から環境依存部分を解決できるように変数を展開する。

serverless.yml
service: cognito-triggers

provider:
  name: aws
  runtime: nodejs12.x

  stage: ${opt:stage}
  profile: ${self:custom.environments.${opt:stage}.profile}
  region: ${self:custom.environments.${opt:stage}.region}

  environment:
    API_HOST_NAME: ${self:custom.environments.${opt:stage}.apiHostName}

plugins:
  - serverless-prune-plugin

custom:
  prune:
    automatic: true
    number: 5
  environments:
    dev: ${file(./environments/dev.yml)}
    stg: ${file(./environments/stg.yml)}
    prd: ${file(./environments/prd.yml)}

functions:
  pre-sign-up:
    name: ${self:custom.environments.${opt:stage}.functionNamePrefix}cognito-trigger-pre-sign-up
    handler: pre-sign-up.exec
  user-migration:
    name: ${self:custom.environments.${opt:stage}.functionNamePrefix}cognito-trigger--user-migration
    handler: user-migration.exec
  post-authentication:
    name: ${self:custom.environments.${opt:stage}.functionNamePrefix}cognito-trigger--post-authentication
    handler: post-authentication.exec
  post-confirmation:
    name: ${self:custom.environments.${opt:stage}.functionNamePrefix}cognito-trigger--post-confirmation
    handler: post-confirmation.exec

CIrcleCI で継続的にtest実行するための設定

詳細は割愛しますが、↓のように、install, test 実行を追加。

.circleci/config.yml
      
    steps:
      - run:
          name: install dependencies for lambda/cognito-triggers
          command: npm install
          working_directory: lambda/cognito-triggers
      :
      - run:
          name: run tests for lambda/cognito-triggers
          command: npm test
          working_directory: lambda/cognito-triggers
      

まとめ

こんな感じで、ローカル環境での開発、テスト、テスト後のDeployまで一気通貫できました。さらに良いプラクティスがあればフィードバック頂けたら幸いです!

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?