LambdaとAPIGatewayを使ってgitbucketでのcommitをbacklogに送る方法


Backlogでチケットを管理していて、Gitbucketをリポジトリとして使っている場合に
GitbucketへのcommitをBacklogのチケットに反映する方法

[@colorrabbitさんのこの記事](http://qiita.com/colorrabbit/items/5cdadc55996e89e4a4c4)の焼き直しです
Backlog APIのv2に対応させています


## 必要な手順

1. backlog apiを使う為のapi keyを取得
2. LambdaとAPI Gatewayを設置
3. gitbucket側のwebhookにbacklogを追加
4. コミットメッセージにbacklogのチケットIDを含める


## backlog apiを使う為のapi keyを取得

backlogに入り、個人設定 -> APIを開く

適当なAPIのメモを入力し「登録」をするとAPIキーが発行されるのでメモしておく

![Kobito.QEgVEb.png](https://qiita-image-store.s3.amazonaws.com/0/60056/960c5163-63f3-e6ad-937d-45dbeacad2f6.png "Kobito.QEgVEb.png")



## LambdaとAPI Gatewayを設置

### Lambda設定

gitbucketのwebhook -> lambda -> backlog となるようにlambdaを構成。

まずaws consoleからLambdaに入り、Node.jsの6.10を選択、Blank Functionを選択

![Kobito.XZSrgA.png](https://qiita-image-store.s3.amazonaws.com/0/60056/7a15ab83-4884-2de2-135e-7a8d97f33274.png "Kobito.XZSrgA.png")

トリガーはとりあえず作らないで次へ

![Kobito.FgA1X1.png](https://qiita-image-store.s3.amazonaws.com/0/60056/ea0b143f-3581-cabd-2fca-056fa5b8e01d.png "Kobito.FgA1X1.png")


### コードをバリバリ書く。

基本はgitbucketからcommit/push等でhookされた情報を受取り、
いい感じに加工してbacklogのチケットのコメントとして追加してやればOK。

なお、環境変数は以下の感じに定義している

![Kobito.HlyOhr.png](https://qiita-image-store.s3.amazonaws.com/0/60056/850a6bd4-1b7c-ba27-d2a9-e51a0343a8f7.png "Kobito.HlyOhr.png")


書きなぐりのコードだけれど参考までに貼っておきます

```
console.log('--- Gitbucket to Backlog api start');

const response = {
    statusCode: 200,
    headers: {},
    body: ""
};

const querystring = require('querystring');
const http = require('https');
const url = require('url');

const backlog_endpoint = process.env.BACKLOG_ENDPOINT;
const backlog_api_key = process.env.BACKLOG_API_KEY;
const project_id = process.env.PROJECT_ID;

const makePushMessage = (json) => {
    return `''Git Push通知''\n\n ${pushMessage(json)}`;
};

const pushMessage = (json) => {
    return `
Pushed Repository
|~Repository|${json.repository.full_name}|
|~Branch|${json.ref}|
|~Compare|[[view:${json.compare}]]|
`;
};

const makeCommitMessage = (commit) => {
    return `\n${commitMessage(json)}`;
};


const commitMessage = (commit) => {
    return `
|~Commit ID|${commit.id}|
|~Commit Message|${commit.message.replace(/\r?\n/g,"")}|
|~Commiter|${commit.committer.name}|
|~Source|[[view:${commit.url}]]|
`;
};

const getTicketId = (message) => {
  const filtered = message.split(' ').filter((element, index, array) => {
    return (element.includes(project_id));
  });
  return filtered[0];
};

const isFix = (message) => {
    return (
        message.includes('#fix') ||
        message.includes('#fixes') ||
        message.includes('#fixed')
    );
};

const isClose = (message) => {
    return (
        message.includes('#close') ||
        message.includes('#closes') ||
        message.includes('#closed')
    );
};

const makeCommitInfo = (commit) => {
    return {
        ticketId: getTicketId(commit.message),
        isFix: isFix(commit.message),
        isClose: isClose(commit.message),
        commitMessage: commitMessage(commit)
    };
};


const postComment = (ticketId, content) => {
    return new Promise((resolve, reject) => {
        const endpoint = backlog_endpoint + '/issues/' + ticketId + '/comments' + '?apiKey=' + backlog_api_key;

        const req_opts = url.parse(endpoint);
        req_opts.method = 'POST';
        req_opts.headers = {'Content-Type': 'application/x-www-form-urlencoded'};

        const req =
            http.request(
                req_opts,
                (res) => {
                    if (res.statusCode === 200 || res.statusCode === 201) {
                        return resolve();
                    } else {
                        console.log('error, status: ' + res.statusCode);
                        res.on('data', (chunk) => {
                            console.log('BODY: ' + chunk);
                        });
                        return resolve();
                    }
                    
                }
            )
        ;
        
        req.on('error', (error) => {
          console.log('probrem with request: ' + error.message);
          return resolve();
        });

        const postData = querystring.stringify({
            content: content,
            notifiedUserId: [],
            attachmentId: []
        });

        req.write(postData);
        req.end();

    });
};


exports.handler = (event, context, callback) => {

    const pushMessage = makePushMessage(event);

    const commits = ((event) => {
        if (!event.commits) return undefined;
        return event.commits.map((commit, index, array) => {
           return  makeCommitInfo(commit);
        });
    })(event);

    if (!commits) return;

    // sequential execute
    commits.reduce((current, args) => {
        return current.then(() => {
            return postComment.apply(null, [args.ticketId, pushMessage + args.commitMessage]);
        });
    }, Promise.resolve());


    callback(null, response);
};
```

なお、gitbucketのsettings -> webhookで適当にtestしてみるとgitbucketからの
payloadが表示されるのでそれをテストデータにしてLambdaを動かしていくと早い


### API Gateway

[以前の記事](http://qiita.com/AkihiroTakamura/items/86fb1f1eded74e93ec6d)を参考に立てればOK

今のところgitbucket側からいい感じに認証キーを送る方法がわからなくて
API Gateway側は無認証で動かしている（良くないので対処検討中）


## gitbucket側のwebhookにbacklogを追加

gitbucket -> 対象のリポジトリ -> Settings -> Service Hooks

を開き、Add webhookをクリック

payload URLにapi gatewayで作成したurlを指定

![Kobito.uPVlk7.png](https://qiita-image-store.s3.amazonaws.com/0/60056/ef2f8dca-a9cd-2997-eb04-6e9c1d4d5b7f.png "Kobito.uPVlk7.png")


## コミットメッセージにbacklogのチケットIDを含める

以下のようにしてcommit/pushすればbacklog側にコメントが飛ぶ

```
git commit -m 'add readme [backlogのチケットID]'
```

## TODO

* 今はcommitだけを拾ったが、issueとかpull requestとかも拾っていきたい
* gitbucket -> API Gatewayの認証(最悪IPで絞る)
* ちゃんとPromiseの非同期していないので対処したい（array分逐次実行にしないと）