LINE BOTアプリケーションのエラーを自分のLINEアカウントに通知する

Visual StudioとAzure Functionsで作るLINE BOTアプリケーション入門 - pierre3のブログの続きです。

LINE BOT 内のエラーを自分のLINEアカウントに通知する

BOTアプリケーション内で発生した(処理できない)例外の内容等は、Logに記録するだけでも良いのですが、自分のLINEアカウント宛に通知できるようにするとデバッグが非常にはかどります。

f:id:pierre3:20171008215758p:plain:w300

BOTアカウントを管理するLINEアカウント(自分のLINEアカウント)のユーザーIDはLINE Developers consoleのChannel基本設定ページで確認できます。このユーザーIDにエラーの内容をPush通知するように設定します。

f:id:pierre3:20171008222411p:plain:w600

LINE BOT Functionでの使用例

通知先のユーザーIDはアプリケーション設定に登録しておきます。
Azure ポータルで以下の様に設定しておきます。今回は"DebugUser"という名前で追加しました。

f:id:pierre3:20171008145027p:plain:w500

あとは、LINE Messaging APIがエラーレスポンスを返した際の例外処理に、エラーの詳細をPush通知するコードを記述すればOKです。

public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
    //Webhookイベントを取得する
    IEnumerable<WebhookEvent> events;
    try
    {
        var channelSecret = System.Configuration.ConfigurationManager.AppSettings["ChannelSecret"];
        events = await req.GetWebhookEventsAsync(channelSecret);
    }
    catch (InvalidSignatureException e)
    {
        return req.CreateResponse(HttpStatusCode.Forbidden, new { Message = e.Message });
    }
    
    //Webhookイベントを処理する
    try
    {
        var connectionString = System.Configuration.ConfigurationManager.AppSettings["AzureWebJobsStorage"];
        var tableStorage = await LineBotTableStorage.CreateAsync(connectionString);
        var blobStorage = await BlobStorage.CreateAsync(connectionString, "linebotcontainer");
        var app = new LineBotApp(lineMessagingClient, tableStorage, blobStorage, log);
        await app.RunAsync(events);

    }
    //LINE Messaging APIがエラーレスポンスを返した時の例外
    catch (LineResponseException e) 
    {
        //ログ出力        
        log.Error(e.ToString()); 
    
    //アプリケーション設定に登録しておいたユーザーIDにPush通知
        var debugUserId = System.Configuration.ConfigurationManager.AppSettings["DebugUser"];
        if (debugUserId != null)
        {
            await lineMessagingClient.PushMessageAsync(debugUserId, e.ResponseMessage.ToString());
        }
    }
    catch (Exception e)
    {
        log.Error(e.ToString());
    }

    return req.CreateResponse(HttpStatusCode.OK);
}

Visual StudioとAzure Functionsで作るLINE BOTアプリケーション入門

以前の記事で紹介した LINE BOT開発用のAzure Functionsプロジェクトテンプレートですが、 サンプルコードを大幅に追加しました。
(詳しくはMarketplaceのRelease Notesを確認してみてください。)

marketplace.visualstudio.com

この機会に是非、使ってほしい!ということで、今回はLine.Messagingライブラリを使ったAzure FunctionsのLINE BOTアプリケーション開発について少し解説してみたいと思います。

目次

Visual Studio とAzuzre Functionsで作るLINE BOTアプリケーション入門

はじめに

ここで紹介するサンプルコードは、拙作Line.Messagingクラスライブラリを使用することを前提としています。

www.nuget.org

この記事の内容を実際に試したい方は、MarketPlaceに記載のクイックスタートガイド参考に開発環境を作っておきましょう。
(導入時の問題、質問等ありましたら、このブログのコメント欄でも、MarketplaceのQ&AでもTwitterでも何でも良いのでご連絡ください)

Webhook イベントを処理する

友だち追加やメッセージ送信などのイベントは、指定したAzure FunctionsのURLにHTTP POSTリクエストとして送信されます。 そのリクエストによって、HttpTriggerFunctionクラスのRunメソッドが実行されます。

以下は、Line.Messagingを使用したHttpTriggerFunctionの実装例です。

public static class HttpTriggerFunction
{
    //LINE Messaging API クライアントの初期化
    static LineMessagingClient lineMessagingClient;
    static HttpTriggerFunction()
    {
        lineMessagingClient = new LineMessagingClient(System.Configuration.ConfigurationManager.AppSettings["ChannelAccessToken"]);
        var sp = ServicePointManager.FindServicePoint(new Uri("https://api.line.me"));
        sp.ConnectionLeaseTimeout = 60 * 1000;
    }

    [FunctionName("LineMessagingApiSample")]
    public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
    {
        //WebhookのリクエストBodyからイベントオブジェクトを取得
        IEnumerable<WebhookEvent> events;
        try
        {
            var channelSecret = System.Configuration.ConfigurationManager.AppSettings["ChannelSecret"];
            events = await req.GetWebhookEventsAsync(channelSecret);
        }
        catch (InvalidSignatureException e)
        {
            return req.CreateResponse(HttpStatusCode.Forbidden, new { Message = e.Message });
        }

        //イベントを処理する
        try
        {
            var app = new EchoBotApp(lineMessagingClient,log);
            await app.RunAsync(events);
        }
        catch (Exception e)
        {
            log.Error(e.ToString());
        }
        return req.CreateResponse(HttpStatusCode.OK);
    }
}

LINE Messaging APIクライアントを初期化する

メッセージのリプライ、プッシュ通知などのLINEサーバーへのリクエストにはLine.Messaging.LineMessagingClientクラスを使用します。 このクラスは内部でSystem.Net.Http.HttpClientクラスを使用しています。
これをHttpTriggerFunctionクラスのStaticフィールドに1インスタンスだけ作成し、使いまわすようにします。

インスタンスを使いまわす理由は、以下を参照ください。

リクエストBodyからWebhook Event Objectを取得する

Webhookイベントの内容は、リクエストBodyにJSONで格納されています。
Line.Messagingライブラリでは、Run関数の引数HttpRequestMessage reqからEvent Objectを取得する拡張メソッドを用意しています。

static Task<IEnumerable<WebhookEvent>> GetWebhookEventsAsync(this HttpRequestMessage req,strin channelSecret);

このメソッドは以下の処理を行います。

  • リクエストの構文検証(Signature Validation)を行い、NGの場合は例外を投げる
  • リクエストBodyのJSONをパースしてWebhook Event Object(のコレクション)を返す
[FunctionName("LineMessagingApiSample")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
    IEnumerable<WebhookEvent> events;
    try
    {
        var channelSecret = System.Configuration.ConfigurationManager.AppSettings["ChannelSecret"];
        //Webhook Event Objectの取得
        events = await req.GetWebhookEventsAsync(channelSecret);
    }
    catch (InvalidSignatureException e)
    {
        //構文検証エラー
        return req.CreateResponse(HttpStatusCode.Forbidden, new { Message = e.Message });
    }

    //...
}

Webhook Event Object

Webhook Event Object はイベントのタイプ別に7種類用意されています。
Line.Messagingライブラリでは、WebhookEvent抽象クラスのサブクラスとして実装されています。

public abstract class WebhookEvent
{
    public WebhookEventType Type { get; }
    public WebhookEventSource Source { get; }
    public long Timestamp { get; }
}
  • MessageEvent
    ユーザーからメッセージが送られた際に発行されるイベント。
    メッセージには、送られてくるデータの種類によって異なる以下のタイプがあります。

    • text (文字列メッセージ)
    • image(画像データ)
    • audio(オーディオデータ)
    • video(ビデオデータ)
    • file(ファイル)
    • location(位置情報)
    • sticker(スタンプ
  • FollowEvent
    ユーザーがBOTアカウントをフォローした際に発行されるイベント

  • UnfollowEvent
    ユーザーにBTOアカウントをブロックされた際に発行されるイベント
  • JoinEvent
    BOTアカウントがグループやトークルームに参加した際に発行されるイベント
  • LeaveEvent
    BOTアカウントがグループやトークルームから退室させられた際に発行されるイベント
  • PostbackEvent
    テンプレートメッセージに設定したPostbackアクションが実行された際に発行されるイベント
  • BeaconEvent
    LINE Beaconデバイスの受信圏内に入った(出た)際に発行されるイベント

※各イベントの詳細はLINE公式のAPIリファレンスを参照ください。

WebhookEventSource

WebhookEventクラスは、イベントの送信元を表すSourceプロパティを持ちます。

Line.MessagingライブラリではWebhookEventSourceクラスとして実装しています。

public class WebhookEventSource
{
    public EventSourceType Type { get; }
    public string Id { get; }        
    public string UserId { get; }
}

送信元のタイプは以下の3種類です。

  • User (ユーザーと1対1のトークで発生するイベントに付与されます)
  • Group (トークグループで発生するイベントに付与されます)
  • Room (トークルーム(複数ユーザー間でのトーク)で発生するイベントに付与されます)

また、Idプロパティには、各送信元を示すIDが格納されます(User ID、Group ID、Room ID)。
UserIdには、各送信元から発信したユーザーのIDが入ります。

イベントを処理する

あとは、リクエストBodyから取得したWebhookEventオブジェクトの内容を見てそれに応じた処理を記述すればOKです。
これをHttpTriggerFunctionクラスに直接記述しても良いのですが、Line.Messagingライブラリではイベントの種類に応じて処理を振り分けて実行するためのクラスが用意されているので、これを使います。

WebhookApplicationクラスを使用する

WebhookApplicationクラスは、RunAsyncというメソッドでWebhookEventのコレクションを受け取り、イベントの種類に応じて各イベントの処理(On~ 仮想メソッド)を呼ぶだけのクラスです。

public abstract class WebhookApplication
{
    protected virtual Task OnMessageAsync(MessageEvent ev) => Task.CompletedTask;
    protected virtual Task OnJoinAsync(JoinEvent ev) => Task.CompletedTask;
    protected virtual Task OnLeaveAsync(LeaveEvent ev) => Task.CompletedTask;
    protected virtual Task OnFollowAsync(FollowEvent ev) => Task.CompletedTask;
    protected virtual Task OnUnfollowAsync(UnfollowEvent ev) => Task.CompletedTask;
    protected virtual Task OnBeaconAsync(BeaconEvent ev) => Task.CompletedTask;
    protected virtual Task OnPostbackAsync(PostbackEvent ev) => Task.CompletedTask;

    public async Task RunAsync(IEnumerable<WebhookEvent> events)
    {
        foreach (var ev in events)
        {
            switch (ev.Type)
            {
                case WebhookEventType.Message:
                    await OnMessageAsync((MessageEvent)ev).ConfigureAwait(false);
                    break;
                case WebhookEventType.Join:
                    await OnJoinAsync((JoinEvent)ev).ConfigureAwait(false);
                    break;
                case WebhookEventType.Leave:
                    await OnLeaveAsync((LeaveEvent)ev).ConfigureAwait(false);
                    break;
                case WebhookEventType.Follow:
                    await OnFollowAsync((FollowEvent)ev).ConfigureAwait(false);
                    break;
                case WebhookEventType.Unfollow:
                    await OnUnfollowAsync((UnfollowEvent)ev).ConfigureAwait(false);
                    break;
                case WebhookEventType.Postback:
                    await OnPostbackAsync((PostbackEvent)ev).ConfigureAwait(false);
                    break;
                case WebhookEventType.Beacon:
                    await OnBeaconAsync((BeaconEvent)ev).ConfigureAwait(false);
                    break;
            }
        }
    }
}

このクラスを継承したクラスを作成し、必要な仮想メソッドをオーバーライドして処理を記述します。
以下に、ユーザーのメッセージをオウム返しするだけの処理の実装例を示します。

//オウム返しBOTアプリケーション
public class EchoBotApp:WebhookApplication
{
    private LineMessagingClient MessagingClient {get;}
    private TraceWriter Log{get;}

    //コンストラクタでLineMessagingClientのインスタンスを渡す
    public EchoBotApp(LineMessagingClient messagingClient, TraceWriter log)
    {
        MessagingClient = messagingClient;
        Log = log;
    } 

    //メッセージEventを処理する
    protected override async Task OnMessageAsync(MessageEvent ev)
    {
        //Textメッセージにだけ返信する
        if(ev.Type != EventMessageType.Text){return;}

        return MessagingClient.ReplyMessageAsync(ev.ReplyToken, ((TextEventMessage)ev.Message).Text);
    }
}

あとは、HttpTriggerFunctionのRunメソッドで以下の様に記述するだけです。

//WebhookApplicationを継承したクラスでイベントを処理する
var app = new EchoBotApp(lineMessagingClient,log);
await app.RunAsync(events);

まとめ

基本的な使い方は以上です。
次回からは、もう少し具体的な実装例を解説してみたいと思います。

LINE BOT 開発に使えるLINE Messaging API の.Net Standard Libraryと、Visual Studio 2017用プロジェクトテンプレートを作りました

LINEでビンゴゲームができるBOTを作ったのですが、 このBOTで実装したMessaging API のコードをクラスライブラリに切り出してみました。

pierre3.hatenablog.com

Line.Messaging V0.20-alpha

NuGet Galleryで公開しています。

www.nuget.org

  • ターゲットバージョンは.Net Standard 1.3 としました。
  • APIは一通り実装されていますが、テストが不十分のためAlphaリリース版としています。

LINE BOT Function プロジェクトテンプレート

Azure Functionsのプロジェクトに、「Line.MessagingのNuGet参照」と「ユーザーのメッセージを受け取り、オウム返しをするコード」を追加したテンプレートです。

Visual Studio Marketplaceからダウンロードできます。
テンプレートの使い方は、以下のリンク先に記載されています。

marketplace.visualstudio.com

これからLINE BOT を作ってみたいという方は、ぜひこのテンプレートをダウンロートして試してみてください。

GitHubリポジトリ

ソースコード等はこちらから github.com

LINE でビンゴゲームができるBOTを改良しました

BINGO BOT

以前に紹介した、LINE BOTを改良しました。

pierre3.hatenablog.com

改良したところ

  • リッチメニューに対応しました
  • ビンゴカードを画像で取得できるようになりました
  • ゲーム参加者同士の簡易チャットが可能になりました

リッチメニューに対応しました。

f:id:pierre3:20170809224836p:plain:w300

画面下部のメニューバーに追加した「BINGO Menu」をタップすると、上図の様にメニューが表示されます。 メニューの各項目をタップすると、その項目に割り当てられた定型文が送信されます。

メニュー 定型文(コマンド) 説明
ゲームを開始する 開始 新しいゲームを開始します。 このコマンドでゲームを開始したユーザーがゲームの進行役になります。
ゲームに参加する 参加 ゲームの進行役が作成したゲームに参加します。
番号を引く ドロー 番号を引いてゲームを進めます。 ゲームの進行役のユーザーのみ使用可能です。
カードを更新 カード ゲームの進行状況を反映した、最新のカード画像を取得します。ゲームの参加者のみ使用可能です。

f:id:pierre3:20170811075116p:plain:w300

ビンゴカードを画像で取得できるようになりました

f:id:pierre3:20170809225012p:plain:w300

これだけで、だいぶビンゴゲームらしくなりましたね。(デザインはいまいちですが)

ゲーム参加者同士の簡易チャットが可能になりました

これまで、ゲームの進行に必要なキーワード(開始、参加、ドロー、カード、終了)のみを受け付けて、その他のメッセージは無視していました。
今回、上記キーワード以外のメッセージは、同じゲームに参加している全ユーザーに送信するようにしました。

f:id:pierre3:20170809225343p:plain:w300 f:id:pierre3:20170809225518p:plain:w300

上図のように他のユーザーからのメッセージには、先頭に@ユーザー名を付けて送信します。

お試しユーザー募集中!

ちょっと試してみたいと思った方、こちらから友達登録してみてください!

友だち追加

LINE BOT Webhooks の署名検証をC#で実装する

LINE BOT 開発では、ユーザーが送信したメッセージなどのイベントは、指定したURLにHTTPSのPOSTリクエストとして送信されますが、リクエストの送信元が間違いなくLINEからのものであることを確認する必要があります。

LINE API Referenceには以下の様に書かれています。

Signature validation

リクエストの送信元がLINEであることを確認するために署名検証を行わなくてはなりません。 各リクエストには X-Line-Signature ヘッダが付与されています。 X-Line-Signature ヘッダの値と、request body と Channel secret から計算した signature が同じものであることをリクエストごとに 必ず検証してください。

検証は以下の手順で行います。

  1. Channel secretを秘密鍵として、HMAC-SHA256アルゴリズムによりrequest bodyのダイジェスト値を得る。
  2. ダイジェスト値をBASE64エンコードした文字列が、request headerに付与されたsignatureと一致することを確認する。

今回は、これをC#で実装してみましょう。

必要なデータは以下の3つです。

  • リクエストのX-Line-Signature ヘッダに格納されている値
  • リクエストBODYの値
  • Bot アカウントに付与されるChannel Secret(Line DeveloppersのChannels>>Basic Informationで確認できます)

これらを引数にして検証を行う関数を作ります。

using System.Security.Cryptography;
using System.Text;

public bool VerifySignature(string xLineSignature, string requestBody, string channelSecret)
{
    try
    {
        //channnel secret と request body をByte配列に変換する
        var key = Encoding.UTF8.GetBytes(channelSecret);
        var body = Encoding.UTF8.GetBytes(requestBody);
        
        //channel secretをキーにしてHMAC-SHA256アルゴリズムでrequest bodyのダイジェスト値を得る
        using (HMACSHA256 hmac = new HMACSHA256(key))
        {
            var hash = hmac.ComputeHash(body, 0, body.Length);
            //ダイジェスト値をBASE64に変換
            var hash64 = Convert.ToBase64String(hash);
            //X-LINE-Signatureヘッダの値と一致すればOK!!
            return xLineSignature == hash64;
        }
    }
    catch
    {
        return false;
    }
}

これを、Azure Functions(HttpTrigger)で使用する例を以下に示します。

using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    //Channel Secretの取得。Channel SecretはAzure ポータルでアプリケーション設定に登録しておきます。
    var channelSecret = ConfigurationManager.AppSettings["ChannelSecret"]);
    //Request Body の取得
    var contentJson = await req.Content.ReadAsStringAsync();
    //X-Line-Signatureヘッダ値の取得
    var xLineSignature = req.Headers.GetValues("X-Line-Signature").FirstOrDefault()
        
    if (string.IsNullOrEmpty(xLineSignature) ||
        !VerifySignature(xLineSignature, contentJson, channelSecret))
    {
        //検証に失敗
        return req.CreateResponse(HttpStatusCode.Forbidden, new { Message = "Signature validation faild." });
    }

    //BOTでやりたい処理...
}

LINEでビンゴゲームができるBOTを作りました

ビンゴゲームの基本機能をASP.NETのWeb APIで、LINEとの連携部分(BOT)をAzure Functionsで作りました。

例によってソースコードGitHubにて公開していますので、覗いてみてください。

github.com

BINGO Web API

ビンゴゲームの基本的な機能は、Azure App ServiceでホストしたASP.NET CoreのWeb APIで提供します。

次のような、ごく基本的な機能のみをREST APIで提供しています。

  • ゲームを作成する
  • ゲームにカードを追加する
  • 番号を引いてゲームを進める
  • 現在のゲームの状態を取得する
  • 現在のカードの状態を取得する

詳しくは、Swaggerさんが生成してくれたAPIリファレンスをご確認ください。 http://bingowebapi.azurewebsites.net/swagger/

LINE BOT Function

LINE BOT 関連の機能は、Azure Functionsで処理しています。

主に次のような処理を行っています

  • LINEユーザーからのメッセージを受け取る
  • メッセージの内容に応じてゲームの開始、参加、進行などを行う。
  • 状況に応じたBINGO Web APIを呼び出す。
  • ゲームの進行状況、その他メッセージを LINEユーザーに送信する

開発環境、Frameworkとか

BINGO WebAPI

Web APIの開発にはVisual Studio 2017を使用しています。
ASP.NET Core Webアプリケーション」のプロジェクトテンプレートを使用して作成しました。

DB関連はAzure SQL Server + EntityFramework Coreを使用しています。

LINE BOT Function

Azure Function Tools for Visual Studio 2017

LINE BOTの開発には,「Azure Function Tools for Visual Studio 2017」というツールを使用しています。

このツールを使用するとAzure Functions の開発がとてもに楽になります。

※ このツールは(2017年7月現在では) プレビュー版のVisual Studio (Visual Studio 2017 Preview(2))でないとインストールできないようです。 お試しする場合は、ご注意を。

Azure Table Strage

ゲームに参加しているLINEのユーザー情報と、BINGO APIの発行するゲームID、カードIDとの紐づけなどの情報はAzure Table Strageに保存しています。

遊び方

LINE BOTを友達登録します

BINGO BOTというアカウントを友達登録するとお試しで遊ぶことができます。

BINGO BOT

友だち追加

※ ただし、開発中のお試し版であることをご承知の上でご利用ください。 また、予告なくサービスが停止したり、仕様が変更されたりする場合がありますので予めご了承ください。

ゲームオーナーとプレーヤー

BINGOゲームを始めるには、ゲームを作成・進行するゲームオーナー1人と、ゲームに参加するプレーヤー(複数人)が必要です。

ゲームオーナーの操作

ゲームを作成する

まずは、ゲームオーナーがゲームを作成します。
やり方は簡単で、BINGO BOT に「0」または「開始」と送るだけです。

合言葉を決める

するとLINE BOTから、合言葉の設定を促すメッセージが帰ってきます。 合言葉は、プレーヤーがゲームに参加するために必要なキーワードです。
20文字まで好きな言葉を返信してください。 合言葉を設定しないこともできます。(その場合は「なし」と返します)

ゲームIDと合言葉をプレーヤーに伝える

合言葉を送信すると、ゲームが開始されLINE BOT からゲームIDが返信されます。
このゲームIDと、指定した合言葉を参加するプレーヤーに伝えて、入力してもらいます。

番号を引く

あとは、適当な文字をBINGO BOT に送信するだけです。 1回送信するたびに1つ番号を引きます。

ゲームを終了する

ゲーム進行中に「終了」と入力するとゲームを終了できます。(終了するまで次のゲームを遊ぶことができませんので注意)

f:id:pierre3:20170718230822p:plain:w300 f:id:pierre3:20170718231038p:plain:w300 f:id:pierre3:20170718231049p:plain:w300 f:id:pierre3:20170718231102p:plain:w300

プレーヤーの操作

ゲームに参加する

ゲームに参加するには、BINGO BOTに「1」または「参加」と送ります。

ゲームIDと合言葉を入力する

するとLINE BOTからゲームIDと合言葉を入力するよう促すメッセージが返ってくるので、ゲームオーナーから伝えられたそれらを入力します。

カードを取得する

ゲームへの参加に成功すると、BINGOカードが返信されます。
あとは、ゲームオーナーが番号を引くたびに、BINGO BOTから引いた番号が通知されます。
ゲーム進行中、何かメッセージを送ると、ヒットした番号を反映した現在のカードが取得できます。

f:id:pierre3:20170718231127p:plain:w300 f:id:pierre3:20170718231150p:plain:w300 f:id:pierre3:20170718231201p:plain:w300 f:id:pierre3:20170718231205p:plain:w300 f:id:pierre3:20170718231213p:plain:w300

まとめ

まだまだ作り込みが足りていないので、もうしばらくはこいつの開発で楽しめそうです。
とりあえず、BINGOカードは画像にしたいですね。  

あと、今後は開発中にハマったこととか、調べたことなどTIPS的な小ネタを記事にできればいいな。

Azure App Service の継続的配信(プレビュー)を試してみました

Azure App Service の継続的配信(プレビュー)

最近あまり触れていなかったASP.NET系の技術にそろそろじっくり取り組んでみようかな、と思う今日この頃。
ASP.NET Core & Azure App Service で何か作ろうとAzureポータルを触っていると、App Serviceのメニューに「継続的配信(プレビュー)」なる項目を見つけました。
今回はこれを試してみたいと思います。

f:id:pierre3:20170402083040p:plain

どんな機能か

Visual Studio Team Service (VSTS)を利用して、Azure App Serviceへの継続的配信(Continuous Delivery)を行うためのワークフローを構成してくれる機能です。

これを使用することで、GitのリポジトリにコードをPushするだけでVSTSのビルドタスクが走り、コードの取得→ビルド→Unitテスト→デプロイ といった一連の流れを自動化できるようになります。

配信元のソースはVSTSのGit リポジトリ以外にもGitHubリポジトリが指定できます。(その他、GitやSVNのプライベートなリポジトリを指定することもできるようです。)

事前準備

では、早速試してみましょう。今回はGitHubリポジトリと連携させてみます。

サンプルアプリケーション

今回サンプルとして使用するアプリケーションは、Visual Studio 2017のASP.NET Coreのプロジェクトテンプレートを使用して作成したものを使用します。

f:id:pierre3:20170402093448p:plain

  • [ファイル]-[新規作成]-[プロジェクト]で「ASP.NET Core Webアプリケーション(.NET Core)」を選択します。
  • ターゲットフレームワークに「ASP.NET Core 1.1」、テンプレートは「Web API」としました。
  • 「Dockerサポートを有効にする」なるチェックボックスがありますが、今回はOFFで作成します。

こうして作成したWebAPIのアプリケーションを、そのままGitHubに上げておきます。

github.com

VSTSでプロジェクトを作成

Visual Studio Team Service(VSTS)で、このアプリケーション用のプロジェクトを予め作成しておく必要があります。

VSTSのアカウントがない場合は、以下で作っておきましょう。

www.visualstudio.com

アカウントの準備ができたら、プロジェクトを作成しておきます。ここではとりあえずプロジェクト名を決めて作成するだけでOKです。

Azure App Serviceを作成

Azureポータルで、アプリケーションを配置するApp Serviceを作成しておきます。
ここで注意が必要なのは、今回の継続的配信機能を使用するためには、Standerd(S1)以上のサービスプランを選択する必要があるという点です。  
Freeプラン(F1)では使用できないので気を付けてください。

継続的配信(プレビュー)の設定

これで下準備は整いました。早速Azureポータルで「継続的配信(プレビュー)」を使ってみましょう。

詳しい手順は、以下のページ(英語)に掲載されていますので、こちらを参考に進めて頂ければ問題ないと思います。

www.visualstudio.com

動かしてみよう

これで必要な設定はすべて完了し、GitHubにPushするだけで最新のアプリケーションがApp Serviceに配置されるはずです。

はずなのですが・・・

ビルドに失敗します

GitHubへのPushをトリガーにVSTSのビルドタスクが動くところまでは問題なかったのですが、そのままではビルド自体が失敗してしまっていました。

VSTSのサイトで、今回のプロジェクトを開いてビルドタスクの設定を確認してみます。
ビルドタスクの設定は「Build&Release」タブで確認できます。

f:id:pierre3:20170402175617p:plain

  • ビルド設定名部分のリンクをクリックして

f:id:pierre3:20170402175631p:plain

  • Editをクリックすると、以下のようなビルドタスクの設定画面が表示されます。

f:id:pierre3:20170402180140p:plain

ビルドタスクの設定を見直す

既定では、ASP.NET Core(PREVIEW)というテンプレートが使用されるようなのですが、そのままではうまくいきませんでした。
更に、各タスクの設定等を変えていろいろ試してもみたのですが、解決しませんでした。

そこで、ASP.NET Core(PREVIEW)のテンプレートを使うのは止めて、以下のページに掲載されている内容で試してみたところビルドが通るようになりました。

www.visualstudio.com

「Get Sources」を除く既定のタスクを全て削除し、「+Add Task」で新しいタスクに置き換えていきます。

以下に設定内容のスクリーンキャプチャを載せておきます。

f:id:pierre3:20170402211628p:plain

f:id:pierre3:20170402211643p:plain

  • Build Solution
    Visual Studio Buildタスクを実行します。

f:id:pierre3:20170402211659p:plain

  • Test Assemblies
    対象のリポジトリにテストプロジェクトが含まれる場合にテストを実行します。

f:id:pierre3:20170402210917p:plain

f:id:pierre3:20170402211715p:plain

  • Archive Files
    ビルド成果物をZIPに圧縮します。

f:id:pierre3:20170402211745p:plain

  • Publish Build Artifacts
    ビルド成果物をサーバーに公開します。

f:id:pierre3:20170402211810p:plain

Build Agentを変更する

もう1つ。ビルド設定画面で「Options」のタブを選択して、右側の「Default agent queue」を”Hosted”から”Hosted VS2017”に変更しておかないとうまくいきませんでした。

f:id:pierre3:20170402184429p:plain

ここまで終えたら、ビルド設定画面の右上にある「Save & Queue」をクリックして、設定したタスクを実行してみます。

f:id:pierre3:20170402215115p:plain

これで、ビルド成果物を作成するところまでは何とか動かすことが確認できたのですが、今度は次のフェーズで失敗してしまいます。

今度はデプロイに失敗する

ビルド成果物の作成が完了したら、Azure App Serviceへ実際に配置を行うReleaseのフェーズに移行するのですが、ここで次のようなエラーが発生して失敗してしまいます。

error: Web Deploy cannot modify the file - ERROR_FILE_IN_USE

どうやら、配置先のDLLがロックされていて上書きできないことが原因のエラーのようです。

※この問題は、GitHubのIssuesでも議論されていました。

Azure Web App Deployment error: Web Deploy cannot modify the file - ERROR_FILE_IN_USE · Issue #1607 · Microsoft/vsts-tasks · GitHub

VSTSで「Release」の設定を見てみると、以下の様になっていました。

f:id:pierre3:20170402224156p:plain

  • 「Deploy Azure App Service to Slot」でStaging用スロットにデプロイ
  • それが成功したら「Manage Azure App Service - Slot Swap」で本番用のスロットにスワップ

という構成になっています。

今回は「Deploy Azure App Service to Slot」でファイルが上書きできずにエラーとなっていました。
これを解決するには、配置先のStagingスロットのサービスを一旦停止してからデプロイを実行する必要がありそうです。

Releaseタスクの設定を変更する

そこで、以下の様に「Deploy Azure App Service to Slot」の前後にサービスの停止、再開を挟むようにしてみたところ無事デプロイされるようになりました。
以下に設定内容を貼っておきます。

  • Manage Azure App Service - Stop App Service

f:id:pierre3:20170402230003p:plain

  • Deploy Azure App Service to Slot

f:id:pierre3:20170402230047p:plain

  • Manage Azure App Service - Start App Service

f:id:pierre3:20170402230059p:plain

Manage Azure App Service - Slot Swap

f:id:pierre3:20170402230111p:plain

まとめ

非常に長くなってしまいましたが、ようやくこれでGitHubリポジトリからAzure App Serviceへの継続的配信の設定はひとまず完了です。

今回試したのはあくまでも(プレビュー)なので、(プレビュー)が取れた暁にはAzureポータルからポチポチと数クリックするだけで済むようになっているかもしれません。(涙目)