LCL Engineers' Blog

夜行バス比較なび・格安移動・高速バス比較を運営する 株式会社LCL開発者のブログ

BugsnagでRailsのエラーを漏れなく補足する

弊社では、以下のエントリで紹介したように、Bugsnagを利用してエラー監視をしています。

techblog.lclco.com

前回は、JavaScriptでの利用について紹介しましたが、今回はRailsでどのように利用しているのかを紹介します。

Bugsnagの選定理由

Railsのエラー検知サービスとしては、airbrakeやsentry等が有名ですが、Bugsnagは他と比較して費用が手頃でかつUIが見やすかったため選定しました。(それほど時間をかけて、比較検討したわけではありません)

導入手順

Gemfileに下記を追加して、bundle installを実行します。

gem 'bugsnag'

gemを追加すると、以下のコマンドでbugsnagのconfigファイルが生成できます。API KEYはbugsnagの管理画面から取得できます。

bundle exec rails generate bugsnag YOUR_API_KEY_HERE

これだけで、自動的に発生した例外を補足して、Bugsnagへ通知されるようになります。

任意場所に通知を仕込む

通常のケースでは通るはずがないコードにも、Bugsnagを仕込んで検知するようにしています。

例)必ず取得できるはずのデータが取得できなかった場合

Bugsnag.notify(
    RuntimeError.new('not found xxxx '),
    {
        :severity => 'error',
          :errro_detail => 'エラーの詳細・・・',
          :error_data => {:aaa => '・・・', :bbb => '・・・`},
    }
)

errro_detail,error_dataに各種データを設定することで、Bugsnag側で確認できます。

f:id:lcl-engineer:20170226185952p:plain

このように任意場所で通知を仕込むことで、データ不備によるエラーを早期検知することができるようになりました。

ChatWorkへの通知方法

Bugsnagがエラーを検知するとChatWorkへ通知するようにしています。残念ながら、チャットーワークとの連携はデフォルトでは用意されていないので、独自のAPIを用意して実現しています。

NewrelicとChatWorkの連携でも同じことを行っていますので、詳細は下記の記事を御覧ください。

techblog.lclco.com

まとめ

以前は、エラーログを定期的にチェックして、予期していないエラーが発生していないかを確認していましたが、Bugsnagを導入してからは不要になりました。 Bugsnagは導入も簡単で、費用を比較的安いので、非常におすすめのサービスです。

自社サービスの常時SSL(サイト全体HTTPS)対応でやったこと

Webエンジニアの森脇です。

弊社では先日「格安移動」という自社サービスの常時SSL化(サイト全体HTTPS化)へ対応しました。 この記事では、常時SSL対応のためにやったことを紹介したいと思います。

idou.me

なぜHTTPS化したか

GoogleやApple等のHTTPS化への積極的な姿勢を考慮すると、今後HTTPS化対応は避けられないと判断し、早い段階で対応しておいたほうが影響範囲が少ないと考えて対応に踏み切りました。

環境

サイトは以下の環境で構成されています。

  • AWS
  • ALB
  • Varnish
  • Nginx
  • Unicorn(Rails)

ALBまでをHTTPSで通信し、ALBから後ろはHTTPで通信させています。 サイト全体HTTPS化以前も、お問い合わせフォーム等の一部のページはHTTPS通信をしていました。

--- (HTTPS) ---> ALB --- (HTTP) --> Varnish --> Nginx

移行前の事前対応

できるだけ切り替え当日の作業量・影響範囲が少なくなるように、先行して対応できることは対応しておく方針にしました。

サードパーティタグが、HTTPS対応しているかの調査・修正

重要なサードパーティのタグがHTTPSへ対応できていない場合は、サイト全体のHTTPS対応を延期せざるを得ないため、最初に確認をしました。 全て問題ないことが確認できたので、現在httpで指定されているものは、プロトコル省略の形へ書き換えました。

http://
↓
//・・・

内部リンクを絶対から相対へ書き換え

サイトの内部リンクの指定が絶対URLの箇所が存在したため、こちらも事前に相対URLへ変更しました。

http://xxx.xxx.xx/yyy/zzz
↓
/yyy/zzz

キャッシュをHTTPSとHTTPで分ける

格安移動は、Varnishを利用しページをキャッシュしていますが、HTTPS/HTTPでキャッシュが別れておらず、HTTPSでアクセスした場合も、HTTPのキャッシュが返されてしまうという状態でした。そのため、HTTPS/HTTPのアクセスに応じて、キャッシュを別管理にする対応を実施しました。

なお、ALB→Varnishの通信はHTTPSではなくHTTPのため、大元のリクエストがHTTPSであることを判定するにはHTTPヘッダの「X-Forwarded-Proto」を利用して行いました。

sub vcl_hash {

  hash_data(req.url);

  # httpsとhttpでキャッシュを分ける
  if (req.http.X-Forwarded-Proto) {
    hash_data(req.http.X-Forwarded-Proto);
  }

  return (lookup);
}

移行に向けた準備

SSL証明書の導入

先行して一部のページでHTTPS対応を行っていたため、SSL証明書は既に導入済みであり、HTTPS化にともなって特に対応はしていません。

中間証明書の設定が漏れていると、一部のAndroid端末で警告が表示されてしまうため、証明書が正しく設定できているかどうかは、以下のサイトなどで事前チェックしおくことをおすすめします。

https://cryptoreport.geotrust.com/checker/

HTTP→HTTPSへのリダイレクト設定

nginxで301リダイレクトするようにしています。 Varnish→NginxへはHTTPリクエストとなるため、Varnishでの制御同様「http_x_forwarded_proto」で判断をしています。

if ($http_x_forwarded_proto != 'https') {
    return 301 https://$host$request_uri$is_args$args;
}

sitemap.xmlの出力URL変更

googleに送信するための、sitemap.xmlの出力URLをhttp→httpsに変更します。 robots.txtに、sitemap.xmlの配置URLを定義しているため、そちらもhttpsへ変更します。

Sitemap: https://idou.me/sitemap/sitemap.xml.gz
↓
Sitemap: http://idou.me/sitemap/sitemap.xml.gz

Cookieへのsecure属性付与

HTTPSのみCookieを送信するように、secure属性を付与しました。

HSTS対応

HSTSについては、トラブル時の切り戻しを考慮し、まずは対応を見送りましたが、今後対応予定です。

※ HSTSの説明については、下記をご参照ください。

HSTS (HTTP Strict Transport Security) の導入 - Qiita

移行作業

移行は部分的ではなく、一括で行いました。まだまだサイトの規模も小さいため、一括で行ってもそれほどリスクは大きくないと考えました。

大きく以下の流れで移行しています。

HTTP→HTTPSへのリダイレクト設定

設定後に、問題なくリダイレクトされることを確認します。 Google等のクローラーも問題なくHTTPSサイトへアクセスできていることをログで確認します。

Google Search Consoleの設定

HTTPS用に、新規にGoogle Search Consoleへサイトを追加する必要があるため、新たにサイトを追加します。

sitemap.xmlの再作成・送信

sitemap.xmlをHTTPS用のURLで再作成し、Google Search Consoleから送信します。

サイト外のURLの変更

外部のサイトから自サイトへリンクしているものは、自分たちで変更可能なものはHTTP→HTTPSへ変更しました。

  • 各種広告のURL
  • Twitter,Facebook等のURL
  • コーポレートサイトなどの外部サイト
  • 名刺等の紙媒体

移行後のチェック

Googleの順位に変動がないかどうか、HTTPSのサイトが正しくインデックスされるかどうかを重点的にチェックしています。今のところ順位変動はなく、徐々にHTTPSサイトも徐々にインデックスされています。

まとめ

HTTPS化対応は、かなり大変だと考えてましたが、意外と影響範囲は少なく、実質2,3日で対応完了しました。サイトの規模が大きくなるにつれて、対応工数・リスクは当然膨らんでいきますので、できるだけ早めの対応をおすすめいたします。

JavaScriptのエラーをBugsnagで監視しています

フロントエンドエンジニアの岡田です。 先日、夜行バス比較なびを一部React+ES2015化しましたが、これに備えて、LCLでは昨年11月頃からBugsnagでJavaScriptのエラー監視を開始していました。 今回は、Bugsnagについて、簡単な説明と導入方法をまとめました。

Bugsnagとは

Bugsnagは、アプリケーションのエラーを監視して報告してくれるツールです。 LCLではしばらく前から、Bugsnagでバックエンド側(Ruby on Rails)のエラーを監視していましたが、そこにJavaScriptのエラー監視も入れました。

Bugsnag管理画面の使い方

Bugsnagの管理画面は以下のようになっています。 以下の画面はすでに解消したエラーの実際のレポートです。
f:id:lcl-engineer:20170131064355p:plain (件数が多いのは、レポートを受けた時点で実機確認をして動作に問題がなかったので、対応が後回しになったためです)

LCLではYoutrackを使っていますが、連携すると「!」ボタンを押すだけでissueが作成されるので便利です。 他にもGithubやJIRAなどとも連携できるようです。 docs.bugsnag.com

画面左側のTrendやSummariesを見ながら、以下のようなエラーは優先的に対応をするようにしています。

  • 最近急増したエラー
  • スマホのエラー(夜行バス比較なびでは約8割がスマホユーザーのため)

BREADCRUMBSでは、ユーザーがどのような操作をしたときにエラーが起きたのかが見えるので、自分の手元の環境で再現するかどうかを確かめる時に役立ちます。 f:id:lcl-engineer:20170131070205p:plain

設定方法

LCLではすでにRailsの監視を行っていたため、JSの監視のために必要なのは以下のファイルを読み込むだけでした。

<script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-3.min.js" data-apikey="YOUR-API-KEY-HERE"></script>

本番やテストなどのリリースステージを報告させるためには、data-releasestage="{ ステージ名 }" を追加します。

<script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-3.min.js" data-apikey="YOUR-API-KEY-HERE" data-releasestage="production"></script>

上記は本番(production)の例です。ステージ名はRailsで判定して出力しています。 Bugsnag管理画面では以下のように表示され、どの環境で起きたエラーなのかがわかります。 f:id:lcl-engineer:20170131063034p:plain

設定についての詳細は以下のページに載っています。 docs.bugsnag.com

実際の運用

日々のエラーはChatworkへ通知するようにしています。 f:id:lcl-engineer:20170131120904p:plain リリース後で新たなエラーが起きた場合はすぐにチェックしますが、そうでないものは、1日2回程度、軽くチェックしています。

課題

便利なツールですが、課題もあります。

  • Bugsnag管理画面のスレッドの分け方がよくわかりません。違うエラーメッセージでもまとまっていたり、同じようにみえるものも別れたりします。これは私がわかっていないだけかもしれません。
  • 手元の環境では再現しないバグも多く、原因がわからないままのものがあります。
  • エラーの確認に時間がかかります。レアなバグでも問題ないと判定するまでに少し時間がかかるので、優先度の低いものはあまり深追いしないようにしていきたいと思います。

iOS/Androidアプリのリリースフローとチェックリスト

こんにちは、森脇です。

今回は、弊社でのモバイルアプリのリリースフローと、リリース時に利用しているチェックリストについて、可能な範囲でご紹介したいと思います。

リリースサイクル

決まった期間でのイテレーションを回しているわけではなく、次のリリースに入れたい機能などを決めて、それを元にしてリリース目標日を決めるという方法でやっています。

現時点ではモバイルアプリ開発に専従できるリソースがないため、Webサイト側の改善などとのバランスを見て優先度を決定しています。

開発からリリースまでの流れ

git-flowを利用して開発をしており、developブランチへPUSHすると、Bitrise + Beta Crashlyticsで随時テスト版アプリを配布しています。開発対象の機能が完成した時点で、releaseブランチを作成しリリースへと進めます。

releaseブランチを作成した時点で、原則コードフリーズとし、本番環境に近いRC版アプリを配布してテストをします。

RC版でのテストに問題なければ、TestFligthやGoogle PlayのBeta配信でProduction版の最終的なテストを行い、問題がなければ申請・公開をします。

f:id:lcl-engineer:20170130005853p:plain

CI,テスト配布については別記事で詳細を書いています。

リリースチェックリスト

Qiita Teamにチェックリストを用意して、それを元に漏れがないようにチェックしています。

f:id:lcl-engineer:20170131061158p:plain

RC版 配布前の前提条件

まず、RC版を配布できる状態であることを確認します。特にアプリ以外の部分での準備が忘れがちとなるので、それによってリリースが遅れることにならないように、早めに準備が整っているかを確認します。

  • [ ] 今回のバージョンで開発予定の機能は全て消化できているか
  • [ ] アプリから呼び出すAPI側に改修がある場合は、リリース準備はできているか
  • [ ] Webサイトのランディングページの準備はできているか
  • [ ] AppStore / Google Playの更新内容は準備はできているか
  • [ ] リリース後のプロモーションの準備はできているか

RC版アプリでのテスト

この時点でコードフリーズし、2,3日かけて重点的にテストを行います。 テスト版アプリで各機能の詳細なテストは実施済みのため、このフェーズでは端末・機能に漏れがないように網羅的に確認しています。

  • [ ] 今回の開発対象のテスト
  • [ ] シナリオに沿った主要機能でのレグレッションテスト
  • [ ] 新規インストール、バージョンアップで共に問題なく動作するか
  • [ ] Google Analytics等の計測ログが正しく動作するか
  • [ ] 各種OS・端末での確認
    • [ ] Android 4.4
    • [ ] Android 5.x
    • [ ] Android 6.x
    • [ ] iOS 8.x
    • [ ] iOS 9.x
    • [ ] iOS 10.x

Testflight / GooglePlay Beta配信

リリース前の最後のテストとして Testflight ,GooglePlayのBeta配信を行って確認をします。Production版アプリは、RC版と同じソースコードを元にしているため、この段階では細かいテストは行わず、簡単にクラッシュしてしまうなどの致命的な問題がないかを確認しています。

  • [ ] 各種OS・端末での確認
    • [ ] Android 4.4
    • [ ] Android 5.x
    • [ ] Android 6.x
    • [ ] iOS 8.x
    • [ ] iOS 9.x
    • [ ] iOS 10.x

AppStore申請・Google Play公開

最終確認に問題がなければ、申請・公開へと進めます。 問題が発生した場合に対応できるように、公開は原則午前中に実施し、休み前は公開しないことを原則としています。

アプリリリース後

アプリリリース後の数日間は、クラッシュが発生していないかを重点的に監視し、問題があればすぐに対応します。

  • [ ] releaseブランチをmasterにマージ
  • [ ] Crashlyticsなど監視

最後に

以上、弊社でのモバイルアプリのリリースフローについて簡単に紹介しました。 Webアプリケーションのリリースと比べて、まだまだ人力作業が多く非効率な点は否めませんので、品質を保ちつつより効率のよいやりかたとを今後も考えていきたいと思います。

働き方を工夫して効率UP

フロントエンドエンジニアの岡田です。私の仕事は、勤務時間中はほぼPCの前にいなければなりません。そうなると、1日8時間は座って同じ姿勢を続けることになりますが、それは体に良くないそうですし、頭の回転も悪くなる気がします。 そこで、働き方を工夫しています。

在宅勤務を活用してバランスよく働く

一般的な会社では、昼休みを挟んで、午前中3時間+午後5時間くらい働くと思います。 午前中は良いのですが、午後の5時間は少し長すぎて肩がこりますし、集中力にも欠けることがありました。

そこで、在宅勤務を活用します。 LCLでは、在宅勤務の場合は、7:00〜22:00の間で好きな時間に働くことができます。 (お昼前後に一応コアタイムがあります。在宅勤務については以下で詳しくご紹介しています。) techblog.lclco.com

この制度を利用して、以下のように勤務しています。

在宅勤務:7:00〜8:00 1h
   ↓
準備・身支度
   ↓
在宅勤務:9:00〜12:30 3.5h
   ↓
昼休みを兼ねて会社へ移動
   ↓
会社勤務:14:00〜17:30 3.5h

途中で準備の時間をはさんだり、会社へ移動したりすることで、リフレッシュできます。 朝の1時間は少し短すぎると思われるかもしれませんが、その時間でたまったissueを確認したり、他のメンバーのコードレビューやテストをするのにちょうどよいです。

ランチヨガ部

昼休みには週に2回、ヨガをしています。 f:id:lcl-engineer:20161102140454j:plain

当日になるとchatworkでbotが連絡をくれるので、参加したい人が2人以上集まると開催しています。 f:id:lcl-engineer:20170129224852p:plain

だいたい15分程度で、リラックス系のヨガをしています。 ちなみに私のお気に入りは、肩こり解消ヨガです。 www.youtube.com

最近は、瞑想を試したりもしていて、こちらも頭がスッキリして良いですね。

ハイデスクや土足禁止のフリースペースを活用する

最近では、同じ姿勢を続けていると「からだに悪い気がする…!」とそわそわします。 そんなときには、ハイデスクに移動したり、 f:id:lcl-engineer:20161017005556p:plain

土足禁止のフリースペースで働きます。 f:id:lcl-engineer:20160825181310j:plain

オフィスについては以下の記事で詳しく紹介しています。 techblog.lclco.com

以上です。 働き方を工夫して、より短時間で成果をあげていきたいと思います。

jQuery + Railsで作られた非SPAサイトを一部だけReact & ES2015で書き換えました

フロントエンドエンジニアの岡田です。 昨年末に、弊社のサービス:夜行バス比較なびの一部分をReactで書き換えました。

www.bushikaku.net

夜行バス比較なびのJavaScriptは、構築から3年以上たつこともあり、コードの見通しが悪くなってきています。 リグレッションテストなども導入しながら、不具合が起きないように努めてはいますが、テストに時間がかかりすぎるなどの問題がありました。

techblog.lclco.com

そこで今回、Reactを導入して、リファクタリングをしました。 いろいろつまずくところもあったので、この記事では、夜行バス比較なびでどうやってReactを使っているかをご紹介します。 SPAサイトの事例はけっこうありますが、運用中のサイトの一部にだけReactを導入、という事例はあまりなさそうなので参考になれば幸いです。

環境

以下の組み合わせで使っています。
Webpack + babel + ESLint(Airbnb)+ imagemin(画像圧縮)+ browser-sync

webpackの環境は、各自の開発PC(Mac)につくります。 もともとRailsのsprocketsを使っていたため、React化(ES2015化)完了までは今まで通りsprockets を使うことにしました。 つまり、Webpackで書き出したファイルを、sprocketsで管理しているディレクトリ以下へコミットしてしまいます。 (良い方法ではないと思いますが、サーバーにnode.jsの環境を作るまでのつなぎです。)

Webpackの設定

webpack.config.js(開発中に使用)

// 画像圧縮
const imagemin = require('imagemin');
const imageminOptipng = require('imagemin-optipng');
const imageminMozjpeg = require('imagemin-mozjpeg');
const imageminGifsicle = require('imagemin-gifsicle');

imagemin(['images/**/*.{gif,jpg,png}'], '../public/images', {
    plugins: [
        imageminGifsicle(),
        imageminMozjpeg(),
        imageminOptipng(),
    ]
}).then(files => {
    console.log(files);
    //=> [{data: <Buffer 89 50 4e …>, path: 'build/images/foo.jpg'}, …]
});

// エントリーポイントの設定
module.exports = {
  entry: {
    // PC用エントリーポイント
    'es/pc/es2015': './src/scripts/entry/navi/pc.jsx',
    // SP用エントリーポイント
    'es/sp/es2015': './src/scripts/entry/navi/sp.jsx',
  },
  output: {
    // 出力ファイルのベースとなる階層
    // 例:PCは/app/assets/javascripts/es/pc/es2015.js に出力される
    // 出力された /app/assets/javascripts/es/pc/es2015.js はsprocketsへ任せる
    path: '../app/assets/javascripts',
    filename: '[name].js',
  },
  module: {
    preLoaders: [{
      test: /\.(js|jsx)$/,
      loader: 'eslint-loader',
      exclude: /node_modules/,
    },],
    loaders: [{
      test: /\.(js|jsx)$/,
      loader: 'babel-loader',
      exclude: ['/node_modules/'],
    }
  ],
  },
  eslint: {
    configFile: './.eslintrc',
  },
};

webpack-production.config.js(production buildで使用)

const webpack = require('webpack');

module.exports = {
  entry: {
    'es/pc/es2015': './src/scripts/entry/navi/pc.jsx',
    'es/sp/es2015': './src/scripts/entry/navi/sp.jsx',
  },
  output: {
    path: '../app/assets/javascripts',
    filename: '[name].js',
  },
  module: {
    loaders: [{
      test: /\.(js|jsx)$/,
      loader: 'babel-loader',
      exclude: ['/node_modules/'],
    },],
  },
  // 以下の部分でproduction用で書き出し
  plugins: [
    new webpack.DefinePlugin({
      // process.env.NODE_ENVを'production'に置き換える
      'process.env.NODE_ENV': JSON.stringify('production'),
    }),
    // UglifyJsPluginの実行
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        // 圧縮する時に警告を除去する
        warnings: false,
      },
    }),
  ],
};

最初は圧縮&難読化もsprocketsへ任せていましたが、本番環境でJSエラーが出るため、webpackで行うことにしました。

package.json(scripts部分のみ)

{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack --watch & browser-sync start --config ./bs-config.js",
    "build": "webpack --config webpack-production.config.js --progress"
  },
}

ディレクトリ構成

frontend
├── images
├── src
│   └── scripts
│       ├── entry       // エントリーポイントのファイルを設置
│       │   └── navi
│       │       ├── pc.jsx
│       │       └── sp.jsx
│       ├── component     // ページをまたいで使うパーツを設置(js, jsx)
│       │   ├── common
│       │   │   └── xxxxxxxx.js
│       │   └── pc
│       │       └── { コンポーネント名 }
│       │           ├── xxxxxxxx.jsx
│       │           └── xxxxxxxx.jsx
│       ├── page       // ページごとにディレクトリを分けて設置(js, jsx)
│       │   └──  { ページ名 }
│       │       └── sp
│       │           ├── xxxxxxxx.jsx
│       │           ├── xxxxxxxx.jsx
│       │           └── xxxxxxxx.jsx
│       ├── model       // ビジネスロジック
│       │   ├── xxxxxxxx.js
│       │   └── xxxxxxxx.js
│       └── util        // 便利関数
│           ├── xxxxxxxx.js
│           └── xxxxxxxx.js
├── bs-config.js
├── package.json
├── webpack-production.config.js
└── webpack.config.js
  • classは1ファイル1クラス
  • 関数は1ファイルに複数可

エントリーポイントの書き方

Reactを導入しているのは一部のページなので、エントリーポイントで必要な場所にrenderしたり、関数を呼び出すよう指定をします。

// 必要なモジュールをインポート
import React from 'react';
import ReactDOM from 'react-dom';
import History from '../../component/pc/history/History.jsx';

// ページごとに実行する関数や読み込むコンポーネントを指定

if (document.getElementById('history-result')) {
  ReactDOM.render(
    <History />, document.getElementById('history-result'));
}

ちなみにスマホサイトについては、jsのファイルの容量が増えることによりパフォーマンスに影響を与える可能性があるため、現時点では対象ページのみReact & ES2015ファイルを読み込んでいます。

今後の課題

まだ手探りで進めていて、今後も順次置き換え・リリース予定です。 今のところ課題は以下のとおりです。

  • メインとなる検索結果ページは、Ajaxでできていることもありjsのコード量が特に多いので、どのようにReact化を進めるか(分割して進められるのか?)
  • リリースフローの見直し(production buildはサーバーへ任せたい)

これらもまた解決したらご報告したいと思います。

gradle-play-publisherを利用したGoogle Play申請の自動化

新年早々ですが、先日 Andriod版「高速バス比較 」アプリをリリースしました。

圧倒的なバス便の掲載数に、わかりやすい最安値比較、細かい条件での検索など、高速バス・夜行バスを探すためにはなくてないらないアプリです。

play.google.com

f:id:lcl-engineer:20170108005847p:plain

なお、iOS版は昨年の7月にリリースしており、現在では10万以上のダウンロードをしていただいております。

高速バス比較 - 国内の路線と最安値を検索するアプリ on the App Store

さて、今回はAndriod版アプリの開発で行った、Google Play申請の自動化について、ご紹介します。

自動化のメリット

申請は、Google Play Developer Consoleから、必要な情報を入力し、画像をアップロード、APKのアップロードをすれば行えます。複雑なUIでもないので、それほど手間でもありません。

ただ、エンジニアであれば、ありとあらゆるデータをgitでバージョン管理して、全ての操作をコマンドで行いたいと考えるかと思います。アプリ説明や画像の軽微な変更する場合、目視では「変更点どこ?」ってなりますが、gitだと明確ですし、コマンド操作ができれば、CIサービスとの連携で誰でも申請ができるようになります。

自動化で利用するツール

Google Play Developer APIを利用することで、申請を自動化できます。申請ツールは、APIを利用して自作もできますが、gradelプラグインであるgradle-play-publisherを利用しました。

GitHub - Triple-T/gradle-play-publisher: Gradle Plugin to upload your APK and metadata to the Google Play Store

弊社であまり凝った利用はしていませんが、gradle-play-publisherではほぼ必要な機能はそろってると感じてます。

fastlaneのsupplyも良いとの噂を聞くので、iOS版のfastlaneに慣れている場合はこちらもおすすめです。

fastlane/supply at master · fastlane/fastlane · GitHub

事前準備

まず、APIの認証キーを取得する必要があります。これが結構面倒です。

以下を参考にJSONキーを取得します。

Getting Started  |  Google Play Developer API  |  Google Developers

アプリの新規作成はできないため、初回はWebコンソール上からアプリの情報を登録しておきます。

gradle-play-publisherのインストール

build.gradleに下記を追加して、gradle syncします。

buildscript {

    repositories {
        mavenCentral()
    }

    dependencies {
        // ...
        classpath 'com.github.triplet.gradle:play-publisher:1.1.5'
    }
}
apply plugin: 'com.github.triplet.play'
play {
    jsonFile = file("json keyのファイルパス")
}

gradlew tasksを実行すると、Play Store tasksが追加されています。( productFlavorsの定義によって追加されるタスクは異なります)

./gradlew tasks

Play Store tasks
----------------
bootstrapProductReleasePlayResources - Downloads the play store listing for the ProductRelease build. No download of image resources. See #18.
generateProductReleasePlayResources - Collects play store resources for the ProductRelease build
publishApkProductRelease - Uploads the APK for the ProductRelease build
publishListingProductRelease - Updates the play store listing for the ProductRelease build
publishProductRelease - Updates APK and play store listing for the ProductRelease build

Google Playに登録されているデータをダウンロードする

bootstrapReleasePlayResourcesタスクを実行すると、現在 Google Playへ登録されているデータをダウンロードできます。ただし、現時点のバージョンでは画像ファイルはダウンロードできません。

./gradlew bootstrapProductReleasePlayResources

実行すると以下のファイル・ディレクトリが生成されます。

f:id:lcl-engineer:20170108220548p:plain

画像はダウンロードされないので、生成されたディレクトリにファイルを追加します。申請に最低限必要なファイルは、以下の3種類です。

f:id:lcl-engineer:20170108233052p:plain

Google Playに申請情報をアップロードする

デフォルトの状態だと、画像ファイルがアップロードされてないため、build.gradleに「uploadImages = true 」を追加します。

play {
    jsonFile = file("json keyのファイルパス")
    uploadImages = true
}

publishListingReleaseタスクを実行すると、各種ファイルで定義された情報・配置された画像がGoogle Playへ登録されます。

./gradlew publishListingProductRelease

Google PlayにAPKをアップロードする

APKは、手動でアップロードをすると、誤ったAPKを登録してしまうミスが発生しうるので、必ず自動化したいところです。gradle-play-publisherでは、publishApkReleaseタスクでAPKをアップロードできます。

./gradlew publishApkProductRelease

build.gradleに「track = 'beta' 」を追加すると、Beta版としてAPKを登録できます。trackには、「'production'o r 'rollout' or 'beta' or 'alpha'」のいずれかが指定できます。

play {
    track = 'beta' 
    jsonFile = file("json keyのファイルパス")
    uploadImages = true
}

まとめ

上記で記載したとおり、gradle-play-publisherでは非常に簡単に自動化が可能です。リリース時のストレスを少しでも下げるために、申請作業は必ず自動化をおすすめします。