はじめに
本記事は、Supership株式会社 Advent Calendar 2018 - Qiitaの12日目の記事になります。
AWS Lambdaとは
言わずもがな、AWS Lambdaとはサーバーを管理せずコードを実行できるサービスです。
最近の話題だとRubyサポートが始まりましたね。 re:Invent
今後、ますます気軽に利用する人が増えるのではないでしょうか。
管理方法
手軽に利用する反面、ソースなどの管理が煩雑になってしまうのが問題です。
そこで、現在のプロジェクトでの管理方法(node)をご紹介したいと思います!
といいつやっていることは簡単で
- Gitで管理
- Zipに固める(gulp)
- awsにzipでuploadする
上記三点を実現させています。
実践編
今回は例としてRSSを読み込んでSlackに通知するサンプルコードを用いてご説明いたします
準備
AWScli環境を用意(mac)
準備としてローカルからawsにアクセスできるようcliをインストールします。
- awscliをbrewでインストール
$ brew install awscli
- 認証情報を設定
$ aws configure
AWS Access Key ID [******************]: # アクセスキー
AWS Secret Access Key [*******************]: # アクセスシークレット
Default region name [ap-northeast-1]: # デフォルトのリージョン
Default output format [None]: # awscliコマンドの実行結果の出力形式
lambda関数を作成しておく
Gitで管理
こちらは改めて説明するまでもないのですが、普段使い慣れているサービスでok.
今回はgithubを使用します。
ディレクトリは下記のような構成となります。
.
├── README.md
├── circle.yml
├── encrypted-file.txt
├── gulpfile.js # gulpでzip化するのでそのファイル
├── package.json
├── plaintext-file.txt
├── src # Lambdaソースコード
│ └── advent-clendar-notifier
│ └── index.js
└── test-data # Lambdaソースコード内で使用するenvデータを格納
└── advent-clendar-notifier.js
ソースはこんな感じのrssをslackに流すだけのコードを作成
/*
* RSS Notifier to Slack
* https://github.com/blueimp/aws-lambda
*
* Required environment variables:
* - webhook: AWS KMS encrypted Slack WebHook URL.
*
* Optional environment variables:
* - channel: Slack channel to send the messages to.
* - username: Bot username used for the slack messages.
* - icon_emoji: Bot icon emoji used for the slack messages.
* - icon_url: Bot icon url used for the slack messages.
*
* Copyright 2018, Hiroki Hatsushika
*
* Licensed under the MIT license:
* https://opensource.org/licenses/MIT
*/
'use strict';
const ENV = process.env;
if (!ENV.webhook) throw new Error('Missing environment variable: webhook');
let webhook;
const AWS = require('aws-sdk');
const url = require('url');
const https = require('https');
let Parser = require('rss-parser');
let parser = new Parser();
function handleResponse (response, callback) {
const statusCode = response.statusCode;
console.log('Status code:', statusCode);
let responseBody = '';
response
.on('data', chunk => { responseBody += chunk; })
.on('end', chunk => {
console.log('Response:', responseBody);
if (statusCode >= 200 && statusCode < 300) {
callback(null, 'Request completed successfully.');
} else {
callback(new Error(`Request failed with status code ${statusCode}.`));
}
});
}
function post (requestURL, data, callback) {
const body = JSON.stringify(data);
const options = url.parse(requestURL);
options.method = 'POST';
options.headers = {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body)
};
console.log('Request options:', JSON.stringify(options));
console.log('Request body:', body);
https.request(
options,
response => { handleResponse(response, callback); }
).on('error', err => { callback(err); }).end(body);
}
function buildSlackMessage (data) {
return {
channel: ENV.channel,
username: ENV.username,
icon_emoji: ENV.icon_emoji,
icon_url: ENV.icon_url,
text: data.link,
attachments: [
{
fallback: data.title,
title: data.title,
title_link: data.link,
text: data.link
}
]
};
};
function fetchRssContents (url) {
return new Promise((resolve, reject) => {
parser.parseURL(url).then(function (feed) {
if (!feed) {
reject(new Error('Feed Read Error'));
}
const item = feed.items[0];
resolve({
title: item.title,
link: item.link
});
});
});
}
function processEvent (event, context, callback) {
console.log('Event:', JSON.stringify(event));
fetchRssContents(ENV.feed).then((rssData) => {
const postData = buildSlackMessage(rssData);
post(webhook, postData, callback);
}).catch((error) => {
console.log(error);
});
}
function decryptAndProcess (event, context, callback) {
const kms = new AWS.KMS();
const enc = {CiphertextBlob: Buffer.from(ENV.webhook, 'base64')};
kms.decrypt(enc, (err, data) => {
if (err) return callback(err);
webhook = data.Plaintext.toString('ascii');
processEvent(event, context, callback);
});
}
exports.handler = (event, context, callback) => {
if (webhook) {
processEvent(event, context, callback);
} else {
decryptAndProcess(event, context, callback);
}
};
Zipに固める
lambdaのソースができたら、次にGulpを使いZipに固める
npm run dist
に gulp dist
を仕込んでおいて実行!
/dist
フォルダにzipファイルが格納される
実行例
⋊> aws-lambda-ac-2018 on develop ⨯ npm run dist 21:32:24
> advent-clendar-notifier-lambda@1.0.0 dist ~/Documents/myGithub/aws-lambda-ac-2018
> gulp dist
[21:32:33] Using gulpfile ~/Documents/myGithub/aws-lambda-ac-2018/gulpfile.js
[21:32:33] Starting 'dist'...
[21:32:33] Starting 'copy'...
[21:32:33] Finished 'copy' after 56 ms
[21:32:33] Starting 'install'...
[21:32:33] Finished 'install' after 15 ms
[21:32:33] Starting '<anonymous>'...
[21:32:33] Finished '<anonymous>' after 24 ms
[21:32:33] Starting '<anonymous>'...
[21:32:33] Finished '<anonymous>' after 1.02 ms
[21:32:33] Finished 'dist' after 101 ms
Build tag: d526e370-fd40-11e8-965c-b9f3f9ff9183
最後にあらかじめ作っておいたlambda関数にuploadして反映
$ ⋊> aws-lambda-ac-2018 on master ◦ aws --region ap-northeast-1 lambda update-function-code --function-name advendcalendar-rss-to-slack --zip-file fileb://./dist/advent-clendar-notifier-7c2de9a0-fd43-11e8-8915-a941d229fe31.zip --publish
{
"FunctionName": "advendcalendar-rss-to-slack",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXX:function:advendcalendar-rss-to-slack:2",
"Runtime": "nodejs6.10",
"Role": "arn:aws:iam::XXXXXXXX:role/service-role/elastic-beanstalk-events-to-slack",
"Handler": "index.handler",
"CodeSize": 1384,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2018-12-10T13:10:46.583+0000",
"CodeSha256": "XXXXXXXXXXXXXXXX",
"Version": "2",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "XXXXXXXX-610f-4a54-a8e7-d8ed1210a026"
}
まとめ
いかがだったでしょうか!?ちょっと長ったらしく書いてしまいましたが、
やってることは簡単なので、仕組み化してlambdaを楽しみましょう!!
他にも、Encryptionの設定であったり色々あるのですが今回はここまで〜
※ 今回のソースは こちらです。
一緒に働きませんか? 働きましょうよ
私の所属するチームでは、人材を募集しています。
バックエンドからフロントエンドまで幅広い技術を触ってみたい方、
広告配信に興味の方どんどんご応募ください!お待ちしています!