株式会社クリアコード > ククログ

ククログ


SharetaryのためのGitHubアクティビティ定点観測の設定・改

先日、「GitHub上のアクティビティを定点観測してみませんか?」と題してfluent-plugin-github-activitiesSharetaryという2つのソフトウェアを紹介しましたが、Sharetaryの改善に伴っていくつか設定の仕方に変化がありましたので、改めて、最新バージョンでの推奨設定について解説します。 ClearCode Inc. Organizationに所属しているアカウントの公開アクティビティを収集するSharetaryの運用サンプルも、この解説と同じ手順でSharetary 0.5.0に移行しました。

なお、この記事はSharetaryの利用のための技術情報が主ですが、Fluentdのストリームにおける1つの入力を元にして、fluent-plugin-groongaで複数テーブルにレコードを追加する方法の解説にもなっています。 fluent-plugin-groongaを使ったGroongaサーバーのレプリケーションに対して、in_groongaを使わずに直接レコードを登録するやり方に興味がある方も、参考にしてみて下さい。

Sharetary 0.4.0(0.5.0)での変更点

バージョン0.4.0以降のバージョンには様々な変更点がありますが、最大の変更点はスキーマ定義の変更です。

バージョン0.3.0までではほとんどの情報がEventsテーブルに集中していましたが、これにはデータの重複が増えてしまうという欠点がありました。 また、Eventsテーブルのカラム数が増えれば増えるほど管理が煩雑になってしまうという問題もあります。 そこでバージョン0.4.0からは、イベントに直接関係ない情報は別のテーブルに分けて管理するように改めました。

また、それに伴ってイベントに複数のタグを紐付けられるようにし、イベントの属するscope(GitHubのアクティビティに対応するイベントであれば、リポジトリの情報など)はタグの1つとして管理するようにしました。 これにより、「このイベントはfooというリポジトリに関連付けられており、barというハッカソンの期間中に行われた」というような複数の切り口での分類が可能になりました。

(図:Events, Actors, Tagsの各テーブル同士の関係)

このスキーマ変更によって、これまでにクロール済みのイベント情報はそのままでは使えなくなっています。 後述の手順に則って、データを移行する必要があります。

Sharetary 0.3.0からのアップグレード手順

既に設置済みのSharetaryのバージョンを0.3.0から0.4.0以上に更新する手順は以下の通りです。

  1. SharetaryのHTTPサーバ、クローラを停止する。
  2. スキーマ定義変更およびデータ移行用スクリプトをダウンロードする。
  3. ダウンロードしたmigrate-0.3.0-to-0.4.0を、Bashスクリプトとして実行し、データを移行する。
  4. クローラの設定を、新しいスキーマに合わせて更新する。
  5. クローラの動作を再開させる。
  6. sudo npm install -g sharetaryで最新バージョンのSharetaryをインストールする。
  7. SharetaryのHTTPサーバを起動する。

データ移行用のスクリプトは、以下のオプションを指定できます。

  • -h <hostname> : Groongaサーバ(またはDroongaクラスタ)のホスト名。省略時はlocalhost
  • -p <port> : Groongaサーバ(またはDroongaクラスタ)のHTTPサーバがlistenしているポート番号。省略時は10041
  • -t <追加で設定するタグ> : 移行された全てのイベントに固定で付与するタグ。
  • -d : 指定するとdry-runモードとなり、実際にはデータベースに変更を行わないで、どのような変更が行われるのかをシミュレートする。

例えばGroongaサーバがgroongaというホストの10041番ポートで動作していて、収集済みのイベントに「2015-sezemi-readable-code」というタグを付与して移行する場合、コマンドラインは以下のようになります。

$ wget https://raw.githubusercontent.com/clear-code/sharetary/master/bin/migrate-0.3.0-to-0.4.0
$ chmod +x ./migrate-0.3.0-to-0.4.0
$ ./migrate-0.3.0-to-0.4.0 -h groonga -t 2015-sezemi-readable-code -d

意図しないデータ破壊を避けるためは、まず1回dry-runして、適用される変更を先に把握しておくとよいでしょう。

追加するタグの指定は必須ではありませんが、指定しておくのがお薦めです。 過去の運用で何かのハッカソンに関係するイベントを収集済みなのであれば、そのハッカソンの名前をタグとして追加しておくと、それ以後収集するイベントを古いイベントと区別しやすくなります。

クローラの設定をどのように更新するかは、どんなクローラを使っているかによって変わります。 以下は、先日のエントリで紹介したfluent-plugin-github-activitiesの場合の設定(Fluentdの設定)の解説となります。 設定の参考にしてみて下さい。

Sharetary 0.4.0以降のバージョン向けの、fluent-plugin-github-activitiesの設定

先のエントリでも解説していますが、fluent-plugin-github-activitiesは本質的にはSharetaryとは全く独立した存在です。 fluent-plugin-github-activitiesはGitHubのアクティビティをクロールして取得してきてFluentdのストリームに取り込むだけの物で、それを加工してSharetary用のイベント情報としてデータベースに格納するのは、他のプラグインの仕事という事になります。

ここでは、Sharetaryのリポジトリに含まれている設定例の内容を引き合いに出しながら、fluent-plugin-github-activitiesの出力をどのように加工すればSharetary 0.4.0以降のイベント情報として使えるのかを解説します。

レコードの取得

まず最初にfluent-plugin-github-activities用の<source>の設定ですが、ここは先のエントリで紹介した通りで変化はありません。

<source>
  type github-activities
  access_token access-token-of-github
  users ashie,cosmo0920,kenhys,kou,min-shin,myokoym,okkez,piroor
  clients 4
  interval 1
  base_tag github-activity.
  pos_file /var/log/td-agent/github-activities.json
</source>

これによってレコードとして取り込まれたアクティビティ情報を加工する指定が、この後に続く事になります。

レコードを「イベント情報用」「人物用」「タグ用」の3つに複製し、ストリームを分岐する

FluentdでSharetary用のデータベースにレコードを格納するにはfluent-plugin-groongaを使いますが、fluent-plugin-groonga、特にその中でFluentdのストリームからGroongaサーバへの書き込みを行うout_groongaは、流入してきたレコードを特定のテーブル1つのレコードとして格納するという物です。

しかしSharetary 0.4.0以降用には、1つのイベントにつき、イベント情報そのものを格納するEventsテーブル、人物のメタ情報を格納するActorsテーブル、タグのメタ情報を格納するTagsテーブルという具合に、最大で3つのテーブルに同時にレコードを追加する必要があります。

(図:Events, Actors, Tagsの各テーブル同士の関係)

そこで、ストリームを「イベント用」「人物用」「タグ用」の3つに分岐して、それぞれ別々の設定のfluent-plugin-groongaによってレコードとして各テーブルに格納することになります。

(図:Events, Actors, Tagsの各テーブル用にストリームを分岐する様子)

このようなストリームの分岐には、Fluentdに標準添付されているcopyというプラグインを使います。

<match github-activity.**>
  type copy

  # Eventsテーブル用のレコード
  <store>
    type        record_reformer
    enable_ruby false
    tag         event.${tag}
  </store>

  # Actorsテーブル用のレコード
  <store>
    type        record_reformer
    enable_ruby false
    tag         actor.${tag}
  </store>

  # Tagsテーブル用のレコード
  <store>
    type        record_reformer
    enable_ruby false
    tag         repository-tag.${tag}
  </store>
</match>

copyプラグインは、<store>に書かれた別のプラグイン用の指定に基づいて加工を行ったレコードを、Fluentdのストリームに出力します。 この例では、レコードを加工するプラグインであるfluent-plugin-record-reformerを使って、それぞれのタグ名を「何に使うためのレコードか?」という事を示す物に変えてから出力しています。 3つの<store>が書かれているため、これでストリームは3つに分かれることになります。

人物用のレコードを作成し、Actorsテーブルに格納する

話を単純にするために、外部テーブルの方から解説していきます。 まずはActorsテーブルです。

このテーブルのレコードは_keyが人物の名前で、その人物のプロフィールページのURIとなるuri、アイコン画像のicon、人物の重要度を示すclassの3つのフィールドを持ちます。

copyプラグインによって分岐された3つのストリームのうち、Actorsテーブル用に出力しているレコードについて、fluent-plugin-record-reformerの変換ルールを以下のように更新し、上記のフィールドを持ったレコードを出力するようにします。

<match github-activity.**>
  type copy
  ...
  # Actorsテーブル用のレコード
  <store>
    type         record_reformer
    enable_ruby  true
    renew_record true
    tag          sharetary.actor
    <record>
      _key  ${(actor || committer)["login"]}
      uri   https://github.com/${(actor || committer)["login"]}/
      icon  ${self["$github-activities-related-avatar"]}
      class major
    </record>
  </store>
  ...
</match>

この時の設定のポイントは以下の通りです。

  • Acotrsテーブル用のレコードはゼロから組み立てるので、renew_record trueとして、加工元レコードのフィールドを引き継がないようにしています。
  • enable_ruby trueは、セキュリティや速度などの点でデメリットがありますが、今回の用途では必要な設定です。 というのも、fluent-plugin-record-reformerは、プレースホルダーを使って加工元のレコードのフィールドの値を参照できますが、fluent-plugin-github-activitiesが出力するレコード(GitHubのAPIが返すJSONそのままの形式)のようにそれぞれのフィールドが階層化されている場合、欲しい情報を取り出す事ができません。 ですので、必要な情報を参照できるようにするために、この設定を有効化しています。
  • Actorsテーブル用のレコードにはアクティビティの種類を示す情報は必要なくなるので、タグはsharetary.actorに統一しています。
  • 出力するレコードのフィールドのうち_keyuriは、どちらも元のレコードに含まれているユーザアカウント名を使いたいのですが、個々のコミットに対応するレコードと、それ以外のアクティビティのレコードとでは、持っている情報の構成が若干異なるため、参照先を決め打ちできません。 ですので、ここでは||を使って、レコードに存在する方のフィールドを取り出しています。
  • アイコン画像のURIを参照するために、fluent-plugin-github-activitiesが独自に出力している$github-activities-related-avatarというフィールドを参照していますが、値を参照するために${$github-activities-related-avatar}と書くとエラーになってしまいます。これは、フィールド名の先頭の$が特別な意味を持ってしまうためです。ここでは、各フィールドの値を保持している名前空間オブジェクトのOpenStruct自身の[]メソッドを明示的に呼び出す事で、フィールドの値を参照しています。
  • classフィールドの値は、簡単のためmajorに決め打ちしています。元レコードの情報に基づいてclassの値を適宜切り替えると、「受講者」と「メンター」のように、人物によってイベントの表示の仕方に強弱を付ける事もできます。

次は、できあがったレコードをfluent-plugin-groongaを使ってActorsテーブルに格納するための設定です。

<match sharetary.actor>
  type groonga
  store_table Actors

  protocol http
  host localhost

  buffer_type file
  buffer_path /var/spool/td-agent/buffer/groonga-sharetary-actor
  flush_interval 1s

  <table>
    name Actors
    flags TABLE_HASH_KEY
    key_type ShortText
  </table>

  <mapping>
    name uri
    type ShortText
  </mapping>

  <mapping>
    name icon
    type ShortText
  </mapping>

  <mapping>
    name class
    type ShortText
  </mapping>
</match>
  • このテーブルの情報はSharetaryでは直接検索する対象にはしないので、インデックスは作っていません。
  • buffer_pathに指定するパスは、この設定のための固有のパスにする必要があります。 他のテーブル用のfluent-plugin-groongaの設定との間でこの設定の値が衝突すると、どちらか片方の設定しか有効化されませんので、注意して下さい。

これで、人物の情報がアイコン画像のURIなどのメタ情報を伴って保存されるようになります。

タグ用のレコードを作成し、Tagsテーブルに格納する

人物の次は、イベントのタグ付けのためのタグそのものを格納するTagsテーブルです。

fluent-plugin-github-activitiesで取得したアクティビティから取り出せる情報を使って適切なタグを自動設定すれば、収集したイベントを効率よく分類できるようになります。 最もベタな例は、「そのアクティビティに関連しているリポジトリ」という切り口でのタグ付けでしょう。

このテーブルのレコードは_keyがタグの名前で、タグ固有のアイコン画像を示すiconというフィールドを持ちます。 copyプラグインによって分岐された3つのストリームのうち、Tagsテーブル用に出力しているレコードについて、fluent-plugin-record-reformerの変換ルールを以下のように更新し、上記のフィールドを持った「そのアクティビティに関連しているリポジトリ」のタグにあたるレコードを出力するようにします。

<match github-activity.**>
  type copy
  ...
  # Tagsテーブル用のレコード
  <store>
    type         record_reformer
    enable_ruby  true
    renew_record true
    tag          sharetary.tag
    <record>
      _key  ${tag.end_with?(".commit") ? url.match(/repos\/([^\/]+\/[^\/]+)\/commits/)[1] : tag.end_with?(".fork") ? payload["forkee"]["full_name"] : repo["name"]}
      icon  ${self["$github-activities-related-organization-logo"]}
    </record>
  </store>
</match>
  • Acotrsテーブル用のレコードと同様に、Tags用のレコードもゼロから組み立てるので、renew_record trueとしています。
  • 先述の通り、複雑な構造のレコードから必要な情報を取り出す必要があるので、enable_ruby trueとしています。
  • Tagsテーブル用のレコードにもアクティビティの種類を示す情報は不要なので、タグはsharetary.tagに統一しています。
  • 出力するレコードのフィールドのうち_keyの値は、ほとんどのアクティビティではrepo["name"]で参照できますが、コミットに対応するレコードとForkに対応するレコードだけは違う値を使う必要があります。ここでは、3項演算子を使って、元レコードのどのフィールドの値を使うかを振り分けています。

次は、できあがったレコードをfluent-plugin-groongaを使ってTagsテーブルに格納するための設定です。

<match sharetary.tag>
  type groonga
  store_table Tags

  protocol http
  host localhost

  buffer_type file
  buffer_path /var/spool/td-agent/buffer/groonga-sharetary-tag
  flush_interval 1s

  <table>
    name Tags
    flags TABLE_HASH_KEY
    key_type ShortText
  </table>

  <mapping>
    name icon
    type ShortText
  </mapping>
</match>
  • このテーブルもSharetaryでは検索対象にしないので、インデックスは作っていません。
  • 先述の通り、buffer_pathに指定するパスはこの設定のための固有のパスにする必要があります。

これで、タグの情報も保存できるようになりました。

イベント用のレコードを作成し、Eventsテーブルに格納する

最後に、イベントそのものの情報を格納するEventsテーブルです。

GitHubのアクティビティは様々な種類があるため、Sharetary用のイベント情報に変換するためのルールはアクティビティの種類分だけ定義する必要があります。 全てのアクティビティについて完全な変換ルールを定義するのは煩雑なので、共通化できる所はなるべく共通化する方向で設定を組み立てていく事にします。

共通フィールドを埋める

まず最初に、多くのアクティビティに共通している情報を使って共通のフィールドを埋めます。

copyプラグインによって分岐された3つのストリームのうち、Eventsテーブル用に出力しているレコードについて、fluent-plugin-record-reformerの変換ルールを以下のように更新し、共通のフィールドを埋めた状態でレコードを出力するようにします。

<match github-activity.**>
  type copy

  # Eventsテーブル用のレコード
  <store>
    type        record_reformer
    enable_ruby true
    tag         event.${tag}
    <record>
      type        ${tag_suffix[-1]}
      actor       ${actor && actor["login"]}
      source_icon https://github.com/favicon.ico
      timestamp   ${created_at}
      created_at  ${time}
      _repository ${repo && repo["name"]}
    </record>
  </store>
  ...
</match>

設定のポイントは以下の通りです。

  • この後でアクティビティごとに個別の変換ルールを適用するにあたって、元のレコードの各フィールドをその時に参照したいので、今度はrenew_record trueは指定していません。 <record>で定義した各フィールドは、元のレコードへのフィールド追加、または同名フィールドの値の置き換えになります。
  • イベントの種類を示すtypeフィールドの値は、fluent-plugin-github-activitiesが出力したレコードがgithub-activity.pushのようなタグになっている事を利用して、タグの最後のパートをそのまま使います。 tag_suffix[-1]というプレースホルダを使うと、タグ名の最後のパートを簡単に参照できます。
  • イベントの主体となった人物を示すactorフィールドの値はActorsテーブルに格納したレコードの_keyと同じ値を設定する必要があります。 大多数のアクティビティではこれはactor["login"]の値ですが、個々のコミットに対応するレコードでだけは、違うフィールドを参照しなくてはなりません。 そのような特例については、個々のコミットに対応するレコードの変換ルールを定義するときにまとめて処理する事にして、ここではシンプルに「フィールドを参照できるならその値を使い、参照できなければnilにしておく」という書き方にしています。
  • イベントの由来となっているサービスを表すsource_iconの値は、GitHubのfaviconのURIで決め打ちにしています。 他のサービス由来のイベントも収集する場合は、適切な値をその都度指定しましょう。
  • イベントの日時を示すtimestampフィールドの値は、元レコードのcreated_atの値があればそれを使い、無ければnilにするという書き方にしています。 これも、例外となるアクティビティについては別途値を設定します。
  • イベント情報が記録された日時を示すcreated_atフィールドの値は、現在時刻の代替として、Fluentdのレコードに紐付けられている日時情報を参照しています。 (※元レコードのcreated_atと名前が同じですが、意味合いが異なる物で、こちらはあくまでSharetary用のEventsテーブルのカラム名です。)
  • イベントへのタグ付けのために、Tagsテーブルに格納したレコードの_keyと同じ値をtagsフィールドに設定する必要があります。 これもactor同様に、アクティビティの種類によって参照先のフィールドを変えなくてはなりません。 ただ、tagsフィールドの値は後から少し加工をしたいので、ここでは一旦_repositoryという仮のフィールドに値を設定するに留めています。 (後加工の段階で、これを使ってtagsフィールドの値を埋める事になります。)
アクティビティの種類ごとに固有のフィールドを埋める

次に、個々のアクティビティ用の変換ルールを定義します。

例えば、git pushに対応するイベントの変換ルールは、fluent-plugin-record-reformerを使って以下のように書けます。

<match event.github-activity.push>
  type         record_reformer
  enable_ruby  true
  renew_record true
  keep_keys    type,actor,source_icon,timestamp,created_at,_repository
  tag          sharetary.event
  <record>
    _key        https://github.com/${repo["name"]}/compare/${payload["before"]}...${payload["head"]}
    class       minor
    title       Push
    description Pushed ${payload["size"].to_s} commits to ${repo["name"]}
    uri         https://github.com/${repo["name"]}/compare/${payload["before"]}...${payload["head"]}
    reply_uri   https://github.com/${repo["name"]}/compare/${payload["before"]}...${payload["head"]}#commits_bucket
  </record>
</match>
  • この段階では共通のフィールドが埋まった状態のレコードが渡ってくるため、ここではpushに固有の情報を埋めるだけの変換ルールになっています。
  • Eventsテーブル用のレコードはfluent-plugin-github-activitiesが出力するレコードとは全く異なる形式になるため、renew_record trueとして、ゼロからレコードを組み立てるようにしています。 ただし、前の段階ですでに加工を終えた共通のフィールドについてだけは、keep_keys type,actor,source_icon,timestamp,created_at,_repositoryとして値を引き継いでいます。 このようにする事で、「前の段階で共通のフィールドを埋めておいて、この段階では固有のフィールドだけを埋める」という効率の良い設定が可能となります。
  • Eventsテーブル用のレコードとして形式を変換済みということで、タグはsharetary.eventに統一しています。
  • push以外にも、Issueの投稿、コメントの投稿など、多くのアクティビティの変換ルールは同様の要領で定義する事ができます。

別の例として、git commitに対応するイベントの変換ルールも示します。

<match event.github-activity.commit>
  type         record_reformer
  enable_ruby  true
  renew_record true
  keep_keys    type,source_icon,created_at
  tag          sharetary.event
  <record>
    _key        ${html_url}
    class       normal
    title       Commit ${stats["total"].to_s} changes
    description ${commit["message"]}
    extra_description ${files.collect{|file| "#{file["filename"]} (#{file["status"]})" }.join("\n, ")}\n\n${files.collect{|file| file["patch"] }.join("\n\n")}
    actor       ${committer["login"]}
    uri         ${html_url}
    reply_uri   ${html_url}#new_commit_comment_field
    timestamp   ${commit["committer"]["date"]}
    parent      ${self["$github-activities-related-event"] && "https://github.com/#{self["$github-activities-related-event"]["repo"]["name"]}/compare/#{self["$github-activities-related-event"]["payload"]["before"]}...#{self["$github-activities-related-event"]["payload"]["head"]}" || "" }
    _repository ${url.match(/repos\/([^\/]+\/[^\/]+)\/commits/)[1]}
  </record>
</match>
  • 基本的にはpushの場合と同じなのですが、keep_keysでそのまま流用している共通フィールドが、type,source_icon,created_atだけになっています。 これは、commitのイベントの元となるGitHub APIの戻り値が、ここまでで参照してきたアクティビティの共通フォーマットとは異なる形式を取っているからです。 actor, timestamp, _repositoryの各フィールドの値は、ここで改めて、commit専用のフォーマットになっているレコードから適切な値を参照するようにしています。

Sharetaryリポジトリにある完全版の設定ファイルのサンプルには、fluent-plugin-github-activitiesが対応している他のアクティビティも含めた全ての変換ルールが記述されています。 そちらも併せてご参照下さい。

データベース格納前の微調整を行う

アクティビティごとの変換を終えたら、今度はレコードの内容の微調整のための後処理を行います

まず、仮フィールドの_repositorytagsフィールドに変換して、イベントをタグ付けします。 これは、Fluentdに標準添付のrecord_transformerプラグインを使って、以下のような<filter>で実現できます。

<filter sharetary.event>
  type        record_transformer
  enable_ruby false
  remove_keys _repository
  <record>
    tags ["${_repository}"]
  </record>
</filter>

record_transformerの設定は、元になったfluent-plugin-record-reformerと同じような書式で記述します。

  • tagsフィールドの値を、_repositoryフィールドの値が単一の要素になっている配列として定義しています。
  • それと同時に、不要になった仮フィールドの_repositoryを、remove_keys _repositoryでレコードから削除しています。

これだけだと、この段階で変換を行う意味はあまり無いように見えます。 実際、共通フィールドを埋める段階や、各アクティビティ固有のフィールドを埋める段階でも、tagsフィールドを配列として定義することは可能です。

しかし、このようにtagsフィールドの変換を最後の段階に分けておくと、元の情報に含まれていなかった情報を使った、固定のタグの追加が可能になります。 例えば下のようにすれば、各イベントには関連するリポジトリ名のタグだけでなく、sezemi-2015-readable-codeというタグも固定で付与されるようになります。

<filter sharetary.event>
  type         record_transformer
  enable_ruby  false
  remove_keys  _repository
  <record>
    tags ["${_repository}", "sezemi-2015-readable-code"]
  </record>
</filter>

SEゼミの2015年度の取り組みでは4つのサブイベントを開催しますが、サブイベントの名前のタグをこのようにして自動付与すれば、後からサブイベントごとの情報を効率よく検索できるようになります。 また、1つのサブイベントが終わったら上記の後加工段階のフィルタだけを設定し直す事で、参加メンバーが同じだとしても、別のサブイベントの情報を簡単に識別できます。

(最初からtagsフィールドを配列型にしておいて、後処理の段階でtagsの配列に要素を追加する、という形にできればそれが最もスマートなのですが、残念ながら、そのような変換を行えるFluentdプラグインは現時点では無い模様です……そこで、次善の策としてこのようなやり方を考えてみた次第です。)

タグ付けが終わったら、最後の仕上げとして、日時を示すフィールドの値をunixtimeを表す整数値に変換します。 これは、fluent-plugin-filter_typecastプラグインを使って以下のように実現できます。

<filter sharetary.event>
  type  typecast
  types timestamp:time,created_at:time
</filter>

これによって、timestampcreated_atのフィールドの値が、2015-06-15T00:00:00のような文字列から、対応するUTCのunixtimeに変換されます。 Groongaでは日時はunixtimeの整数で表現されますので、この変換は必ず行わなくてはなりません。 (でないと、日付の範囲を指定した絞り込みができません。)

イベント情報をEventsテーブルに格納する

後処理を終えて全てのフィールドが揃ったら、後はEventsテーブルにレコードを格納するだけです。

<match sharetary.event>
  type groonga
  store_table Events

  protocol http
  host localhost

  buffer_type file
  buffer_path /var/spool/td-agent/buffer/groonga-sharetary-event
  flush_interval 1s

  <table>
    name Events
    flags TABLE_HASH_KEY
    key_type ShortText
  </table>

  <table>
    name Timestamps
    flags TABLE_PAT_KEY
    key_type Time
  </table>

  <table>
    name Tags
    flags TABLE_HASH_KEY
    key_type ShortText
  </table>

  <table>
    name Actors
    flags TABLE_HASH_KEY
    key_type ShortText
  </table>

  <table>
    name Terms
    flags TABLE_PAT_KEY
    key_type ShortText
    default_tokenizer TokenBigram
    normalizer NormalizerAuto
  </table>

  <mapping>
    name type
    type ShortText
  </mapping>

  <mapping>
    name class
    type ShortText
  </mapping>

  <mapping>
    name title
    type ShortText
    <index>
      table Terms
      name Events_title_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name description
    type Text
    <index>
      table Terms
      name Events_description_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name extra_description
    type Text
    <index>
      table Terms
      name Events_extra_description_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name tags
    type Tags
    <index>
      table Tags
      name Events_tags_index
    </index>
  </mapping>

  <mapping>
    name uri
    type ShortText
    <index>
      table Terms
      name Events_uri_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name source_icon
    type ShortText
  </mapping>

  <mapping>
    name reply_uri
    type ShortText
  </mapping>

  <mapping>
    name actor
    type Actors
    <index>
      table Actors
      name Events_actor_index
    </index>
  </mapping>

  <mapping>
    name timestamp
    type Time
    <index>
      table Timestamps
      name Events_timestamp_index
    </index>
  </mapping>

  <mapping>
    name created_at
    type Time
    <index>
      table Timestamps
      name Events_created_at_index
    </index>
  </mapping>

  <mapping>
    name parent
    type ShortText
    <index>
      table Terms
      name Events_parent_index
      flags WITH_POSITION
    </index>
  </mapping>
</match>
  • buffer_pathに指定するパスをこの設定のための固有のパスにしている点に注意して下さい。
  • 処理の進行状況によっては、人物やタグのレコードが登録される前にイベントのレコードが登録される事があり得ます。 そのような場合も想定して、ここには外部の参照先であるActorsTagsの両テーブルの定義も含めています。

まとめ

ここまでで設定したすべてのプラグインの関係を図にすると、以下のようになります。

(図:登場した全てのプラグインの関係)

以上、Sharetary 0.3.0をSharetary 0.4.0以降にバージョンアップする際の手順と、Sharetary 0.4.0以降向けにGitHubのアクティビティをクロールするためのFluentdの設定の仕方を解説しました。 その中で、Fluentdのストリームから1つの入力を分岐して複数テーブルにレコードを追加するfluent-plugin-groonga向けの設定の仕方も解説しました。

先日のリーダブルコード勉強会ではまだお試し運用でしたが、今月27日のOSS Hack 4 Beginners来月に予定されているOSS Hack Weekendでは、会場内で行われる開発の様子を横断して眺められることでしょう。 これらのイベントに参加を予定されている方は、会場内のサブスクリーンにもご注目ください。

また、クリアコード関係者の公開アクティビティを収集しているSharetary運用サンプルも引き続き運用しております。 クリアコードの普段の開発の様子も是非ご覧になってみて下さい。

2015-06-18

2015年6月27日(土)にOSS開発参加未経験学生向けOSS開発イベントを開催予定

要約:6月27日(土)にOSS開発参加未経験の学生向けに、OSSの開発に参加する方法をワークショップ形式で教えるイベントを開催します。「OSSの開発に参加してみたいけど漠然とした不安があり手を出せていない…」という学生向けに「具体的にこういう方法で始めるといいよ」という方法を伝えます。ワークショップ形式で実際に手を動かしながら学べるうえに、現役の超優秀エンジニアが多数メンターとしてサポートするので、OSS開発への漠然とした不安を払拭できるはずです。OSSの開発に参加してみたいけど手を出せていなかった学生の人はイベントページから応募してください。背中を押してあげたい学生を知っている人は教えてあげてください。締め切りは6月22日(月)です。

2015年のSEゼミの第3弾のイベントです。2015年のSEゼミは「OSS開発」をテーマにしています。第1弾のリーダブルコード勉強会(開催済み)ではコードを書いてOSSの開発に参加するときに必要となる(開発者にとって)読みやすいコードの書き方を学びました。第2弾のGitHub勉強会(6/20なので今週末開催)ではGitHubを使っているOSSの開発に参加する時に必要となるpull requestの仕方を学びます。

それらに続く第3弾のOSS Hack 4 BeginnersではOSSの開発に参加したことのない学生を対象に、OSSの開発への参加方法を伝えます。このイベントの目的は「OSSの開発に参加してみたいけど漠然とした不安があり手を出せていない…」という学生の「不安」を取りのぞくことです。

不安を取りのぞいた後に控える第4弾のイベントOSS Hack Weekend(7月11日(土)・12日(日)開催)ではのびのびと楽しくOSSの開発に参加しましょう!

内容

OSS Hack 4 Beginnersは学生の不安を取りのぞくために、次のことを大事にした内容になっています。

  • 「やり方」を伝える。
  • 「やり方」を自分でやってみる。
  • やるときは「失敗」しても大丈夫な状況でやる。

これは次の点が「不安」の原因になっていると予想したからです。

  • 「やり方」を知らないから「不安」なのではないか。
  • やったことがないから「不安」なのではないか。
  • 「失敗が怖い」から「不安」なのではないか。

これらの不安の原因を次の方法で解消する内容になっています。

  • オススメのOSS開発参加方法を紹介
  • オススメの方法を実践
  • 対象OSSはメンターが開発に関わっているOSS
  • 超優秀現役エンジニアがメンターとしてサポート

それぞれ説明します。

オススメのOSS開発参加方法を紹介

まず、最初にOSS開発への参加の仕方を説明します。参加の仕方は無数にありますが、「好きなようにやっていいんだよ」と言うと不安が増す原因になってしまいます。そこで、トレーナーがオススメの方法を1つだけ説明し、このイベントではその方法を使って「やってみます」。あえて1つに限定することにより迷いポイントが減り、不安を軽減できるのではないかという考えです。

具体的な方法は次の通りです。

  1. まず動かす
  2. 開発用にインストール
  3. テストを実行

「まず動かす」というのは非常に重要な手順です。重要なことなのに言及している情報を見かけません。一方、pull requestの方法などはよく見かけます。当たり前すぎて誰も言及しないだけなのかもしれませんが、OSSの開発に参加する時に最初にやる一番大事なことは「動かす」ことです。

動かすためにはインストール手順を確認する必要があります。ドキュメントがあればそれを読んで、その手順に従ってインストールし、動かします。なかったら…、開発に参加するチャンスです!

OSSの開発に関わっていない人は、「ドキュメントが足りなくてわかんねぇよ!」とグチって終わりになる場面でしょう。しかし、OSSの開発に参加しようと思っている人にはチャンスです。グチっている時間で直せばよいのです。そうすれば、次にインストールしようとした人は「ドキュメントにちゃんと書いてあってスムーズにインストールできた」と思うことでしょう。直接あなたに感謝の言葉が届くことは稀かもしれませんが、「世界を(ちょっとかもしれないけど)変える」(@_ko1さんからのこのイベント参加者へのメッセージより)ことをしたのです。少しかもしれませんが、その少しが積み重なって大きな変化になっていきます。そうやってOSSはよくなっていくのです。

(なお、このイベントではすぐに対応せずにメモに残しておいて後で対応する、という進め方をします。いきなり「動かす」と「直す」を一緒にやると大変なので、まずは「動かす」に集中します。未経験者向けのイベントなので敷居を下げています。)

「OSSの開発に参加」というと、「コードを読んでバリバリコードを書かないと!」というイメージがあるかもしれませんが、そんなことばかりではありません。コードを書く前の段階でも、ユーザーの目線でフィードバックできることはたくさんあるのです。OSSの開発に参加したことがない人はそれを知らずに敷居が高いと感じてしまっていることでしょう。このイベントを通じて、「そんなことはないんだ、開発に参加したいならできることはいろいろあるんだ」ということをわかってもらいたいです。

「動かす」ことができたら「開発用にインストール」します。「動かす」ときはパッケージを使ってインストールするといったように、ユーザーとしてインストールします。一方、「開発用にインストール」するときはソースコードを取得して、そこから使える状態にもっていきます。

一般的に、「開発用にインストール」するときの方が手間がかかります。しかし、ソースコードを変えて手元で変更を確認できる環境が手に入るというメリットがあります。開発に参加するときはこれは非常に大きなメリットです。

なお、「開発用にインストール」するときもドキュメントがあればそれを参考に作業します。なかったら…、自分がやった手順をメモして、まとめたものを開発者に報告しましょう。開発に参加するチャンスですね。

「開発用にインストール」できたら「テスト」を実行します。多くのOSSではテストが用意されていて、開発者が手元で正しい挙動を確認できるようになっています。テストが用意されていたら実行して、自分の手元でも正しい挙動になっているか確認しましょう。

以上がオススメのOSS開発参加方法です。

イベントではこの内容を説明した上で、実際にトレーナーが前でやってみせます。実際にやっている様子を見ることで理解が進むことを期待しています。

オススメの方法を実践

オススメのOSS開発参加方法を知った後は実際にオススメの方法を実践します。

実践する際はグループを作って、グループごとに進めます。1つのグループは多くても4人から6人です。同じグループでは同じOSSを開発対象にします。これは、グループ内で相談できるようにするためです。

OSSの開発では困ったことがあったら相談しながら進めることは当たり前です。多くの場合、時間的・距離的に離れていることが多いので相談するときはインターネット越しになります。ただし、いきなりインターネット越しに相談しながらやってもいいんだよ、といっても敷居が高いでしょうから、今回は目の前の人たちと相談しやすいように同じグループで同じOSSを開発対象にしました。相談することに慣れてきたらインターネット越しでも相談できるようになることを期待しています。

なお、イベントでは現役の超優秀エンジニアが多数メンターとしてサポートします。メンターはグループ内での相談を促したり、困っていることに対してヒントを与えてくれたりします。メンターとも協力してオススメのOSS開発参加方法を実践し、できるという感触を自分のものにしましょう。

対象OSSはメンターが開発に関わっているOSS

今回開発対象にするOSSはメンターが開発に関わっているOSSにします。当初は学生からの希望も考慮して決めようかと検討していたのですが、学生の「不安」を取り除くことを考えた結果、メンターが開発に関わっているOSSにすることにしました。

理由は次の通りです。

  • 「失敗」に対する「不安」を小さくできそう
    • たとえば新しくissueを作るときに、「こんなissueを立てて嫌がられないかな…」と「不安」に思ってしまったとしても、目の前にいるメンターから「それをやっても大丈夫だよ」と言ってもらえるなら行動に移せるのではないかと期待しています。
  • その場で反応をもらえそう
    • はじめてアクションを起こしたときは相手の反応がくるまでドキドキしてしまうものです。メンターはイベントに参加しているのですぐに反応してくれるはずです。反応待ちでドキドキして作業を進められないといったことを短くすることを期待しています。

まとめ

6月27日(土)に開催するOSS開発参加未経験の学生向けに、OSSの開発に参加する方法をワークショップ形式で教えるイベントを紹介しました。「OSSの開発に参加してみたいけど漠然とした不安があり手を出せていない…」という学生の人はイベントページから応募してください。背中を押してあげたい学生を知っている人は教えてあげてください。あるいは、現役の超優秀エンジニアメンターへのサポートを受けさせてあげたい学生を知っている人はこのイベントへの参加を勧めてください。

なお、締め切りは6月22日(月)です。

すでにOSSの開発に参加していて、現役の超優秀エンジニアメンターのやり方を参考にしたいという学生の人は7月11日(土)・12日(日)に開催するOSS Hack Weekendに応募してください。こちらは学生が希望したOSSを開発対象にしますし、メンターからいろいろ学べる時間がたっぷりあるので、充実した週末になるはずです。

2015-06-17

GitHub上のアクティビティを定点観測してみませんか?

先日、fluent-plugin-github-activitiesSharetaryという2つのソフトウェアをリリースしました。 これらを組み合わせると、GitHub上の個々人の活動をウォッチして簡単に共有することができます。

実際にどのように動作するのか、ClearCode Inc. Organizationに所属しているアカウントの公開アクティビティを収集するSharetaryの運用サンプルをご覧下さい。 (ちなみに、この運用サンプルは別のエントリで紹介したデモンストレーション用のDroongaクラスタをデータの格納先として利用しています。)

この仕組みは、SEゼミの2015年度の取り組みを支援するために開発しました(実は、先日のリーダブルコード勉強会でも会場の片隅で試験運用していました)。 今年のSEゼミではOSS Hack Weekendと題して、学生の皆さんに実際のOSSプロジェクトに参加してもらう予定ですが、Sharetaryの画面を会場内のスクリーンに出しておくことで、イベントの中で実際にどのような開発が行われているかを会場内で目に見える形で共有できますし、プルリクエストが採用されればその様子が一目で分かるというわけです。

GitHub上のアクティビティをクローリングするfluent-plugin-github-activities

fluent-plugin-github-activitiesは、GitHub上の公開アクティビティをクローリングすることに特化したFluentdプラグインです。 以下のような<source>を定義しておくと、設定で列挙したユーザの公開アクティビティを一定間隔で取得して、新規に取得したアクティビティの情報をFluentdのストリームに流すようになります。

<source>
  type github-activities
  access_token xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  users ashie,co-me,cosmo0920,hayamiz,hhatto,kenhys,kou,min-shin,monkey-mas,mooz,okkez,piroor,ridec,s-yata,t2y
  clients 4
  interval 1
  base_tag github-activity.
  pos_file /var/log/td-agent/github-activities.json
</source>

この時レコードとして流れるのは、Events APIの個々のEventです。 どんな種類のアクティビティがあってどんな情報を取得できるのかは、GitHubのAPIドキュメントを参照して下さい。

このプラグインの働きとしてはただそれだけの物なので、Sharetary以外にも様々な用途に流用できます。 例えば社内チャットにアクティビティを自動投稿するというのも、1つの使い方でしょう。

使用の際の注意点として、OAuthのアクセストークンを取得して設定ファイルに書いておく必要があるという点が挙げられます。 GitHub APIは手順に則って取得したアクセストークンを使わない場合、クロールの頻度が最大で60リクエスト毎時までに制限されてしまいます(アクセストークンがあれば5000リクエスト毎時までリクエストできます)。 fluent-plugin-github-activitiesはpushに対応するアクティビティを見付けたときはそれに含まれる各commitの詳細も個別に取得するようになっているため、アクセストークン無しの状態だとすぐにリクエスト過多と判断されてしまいます。

収集されたイベントをタイムライン&アーカイブ表示するSharetary

Sharetaryというのは「Share(共有)」と「Secretary(書記、秘書)」の組み合わせによる造語です。 バックエンドとしてはGroongaまたはDroongaのHTTPサーバを利用する想定になっており、所定の形式に則って定義された「イベント情報」のテーブルに対して検索を行って、新たに増えたイベントのレコードをTwitterなどのタイムライン風にリアルタイム表示することができます。

また、タイムライン表示とは別に、アーカイブ表示というモードもあります。 このモードでは、その時点までに取得済みのイベントのレコードをTogetterのように時系列順で表示することができます。 あるイベントに関連付けられたイベント群はスレッド風に表示されるので、タイムライン表示では追いにくい議論やコメントのやり取りなども、このビューであれば俯瞰して見る事ができます。

なお、Sharetaryで表示するための個々のイベントのレコードには「返信先URI」にあたるフィールドを持たせることができ、例えばGitHub上での「コミット」に対応するイベントに対して「コミットに対するコメントの入力フォーム」のURIを返信先として紐付けておけば、SharetaryのUI上で「このイベントに返信する」というような操作を行った際にそのコメント入力フォームへ遷移させることができます。 そうして追加されたコメントはfluent-plugin-github-activitiesによってクロールされ、Sharetaryのタイムライン上に表れることになります。 このように、Sharetaryはそれ自体に固有の情報をなるべく保持せずに、公開の情報だけをソースとして運用できる設計となっています。

fluent-plugin-github-activitiesとSharetaryの連携

Sharetary自体はデータをクロールする仕組みを持っていないため、fluent-plugin-github-activitiesのように何らかのクローラと併用することが前提になります。 冒頭でも紹介した実際の運用例は、以下の図のような構成になっています。

(droonga1の上に、サービスを提供しているSharetaryと、クローリングしているFluentdが存在する)

この図を見ると、Sharetaryとfluent-plugin-github-activities(Fluentd)は、共通のデータベースとしてDroongaクラスタにアクセスしてはいますが、互いに独立して動作している事が見て取れるでしょう。

fluent-plugin-github-activitiesは収集したアクティビティの情報をそのままレコードとして流すだけなので、それだけではSharetaryの情報ソースには使えません。 実際には、それらのアクティビティをSharetary用の適切なイベント形式のレコードに変換し、fluent-plugin-groongaを使ってSharetary用のデータベースにloadさせる必要があります。

Fluentdでのレコードの形式変換に使えるプラグインはいくつかありますが、元レコードの情報を使って全く新しいレコードを組み立てるという用途には、fluent-plugin-mapが適しているようです。 例えば、「Issueを新たに作成した」というアクティビティをfluent-plugin-github-activitiesがクロールしてきた際に、それを使ってSharetaryのイベントとなるレコードを組み立てる(GitHubのアクティビティをSharetaryのイベントに変換する)ルールは以下のように書けます。

<match github-activity.issue-open>
  type map
  tag "\"sharetary.\" + tag"
  time time
  record (require("time") || true) && { "_key" => record["payload"]["issue"]["html_url"], "type" => "issue-open", "source_icon" => "https://github.com/favicon.ico", "class" => "major", "scope" => record["repo"]["name"], "scope_icon" => record["$github-activities-related-organization-logo"], "title" => "Open", "description" => "Opened new issue: " + "#" + record["payload"]["issue"]["number"].to_s + " " + record["payload"]["issue"]["title"] + ": " + (record["payload"]["issue"]["body"] || ""), "actor" => record["actor"]["login"], "actor_uri" => "https://github.com/#{record["actor"]["login"]}/", "actor_icon" => record["$github-activities-related-avatar"], "actor_class" => "major", "uri" => record["payload"]["issue"]["html_url"], "reply_uri" => "#{record["payload"]["issue"]["html_url"].split("#").first}#new_comment_field", "created_at" => Time.now.to_i, "timestamp" => Time.parse(record["created_at"]).to_i, "parent" => nil }
</match>

fluent-plugin-mapでは変換ルールを文字列として定義しますが、Fluentdの設定ファイルでは文字列型の設定値を改行できないため、このように横長の記述になってしまっています。

基本的にはこの要領でSharetaryに表示させたいアクティビティ用の変換ルールを定義していくのですが、typeがタグと同じ内容だったり、各ルールでactor_classsource_iconが同じだったりする場合、この段階でフィールドを定義するよりも、後でまとめて設定した方がスッキリしますし、今後の変更も容易になります。 レコードのタグなどを使って追加のフィールドを定義するfluent-plugin-record-reformarプラグインを使うと、これらの共通フィールドは以下のようにして一気に設定できます。

<match github-activity.issue-open>
  type map
  tag "\"sharetary.\" + tag"
  time time
  # type, actor_class, source_iconはこの段階では埋めていない
  record (require("time") || true) && { "_key" => record["payload"]["issue"]["html_url"], "class" => "major", "scope" => record["repo"]["name"], "scope_icon" => record["$github-activities-related-organization-logo"], "title" => "Open", "description" => "Opened new issue: " + "#" + record["payload"]["issue"]["number"].to_s + " " + record["payload"]["issue"]["title"] + ": " + (record["payload"]["issue"]["body"] || ""), "actor" => record["actor"]["login"], "actor_uri" => "https://github.com/#{record["actor"]["login"]}/", "actor_icon" => record["$github-activities-related-avatar"], "uri" => record["payload"]["issue"]["html_url"], "reply_uri" => "#{record["payload"]["issue"]["html_url"].split("#").first}#new_comment_field", "created_at" => Time.now.to_i, "timestamp" => Time.parse(record["created_at"]).to_i, "parent" => nil }
</match>

<match sharetary.github-activity.**>
  type record_reformer
  enable_ruby false
  tag completed.${tag}
  <record>
    type ${tag_parts[2]}
    actor_class major
    source_icon https://github.com/favicon.ico
  </record>
</match>

こうして組み立てたレコードは、fluent-plugin-groongaを使ってGroongaまたはDroongaに格納します。 以下はfluent-plugin-groonga用の設定例です。

<match completed.sharetary.**>
  type groonga
  store_table Events

  protocol http
  host droonga0

  buffer_type file
  buffer_path /var/spool/td-agent/buffer/groonga
  flush_interval 1s

  <table>
    name Events
    flags TABLE_PAT_KEY
    key_type ShortText
  </table>

  <table>
    name Timestamps
    flags TABLE_PAT_KEY
    key_type Time
  </table>

  <table>
    name Terms
    flags TABLE_PAT_KEY
    key_type ShortText
    default_tokenizer TokenBigram
    normalizer NormalizerAuto
  </table>

  <mapping>
    name type
    type ShortText
  </mapping>

  <mapping>
    name class
    type ShortText
  </mapping>

  <mapping>
    name title
    type ShortText
    <index>
      table Terms
      name Events_title_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name description
    type Text
    <index>
      table Terms
      name Events_description_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name extra_description
    type Text
    <index>
      table Terms
      name Events_extra_description_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name scope
    type ShortText
    <index>
      table Terms
      name Events_scope_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name scope_icon
    type ShortText
  </mapping>

  <mapping>
    name uri
    type ShortText
    <index>
      table Terms
      name Events_uri_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name source_icon
    type ShortText
  </mapping>

  <mapping>
    name reply_uri
    type ShortText
  </mapping>

  <mapping>
    name actor
    type ShortText
    <index>
      table Terms
      name Events_actor_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name actor_uri
    type ShortText
  </mapping>

  <mapping>
    name actor_icon
    type ShortText
  </mapping>

  <mapping>
    name actor_class
    type ShortText
  </mapping>

  <mapping>
    name timestamp
    type Time
    <index>
      table Timestamps
      name Events_timestamp_index
    </index>
  </mapping>

  <mapping>
    name created_at
    type Time
    <index>
      table Timestamps
      name Events_created_at_index
    </index>
  </mapping>

  <mapping>
    name parent
    type ShortText
    <index>
      table Terms
      name Events_parent_index
      flags WITH_POSITION
    </index>
  </mapping>
</match>

この構成においてはdroonga0droonga1のそれぞれでFluentdが動作していますが、Sharetary関係の物はすべてdroonga1に置いておくという方針で、今回はdroonga1にもfluent-plugin-groongaをインストールして、そこからSharetary用のイベントレコードをloadするようにしています。

Issueの作成以外の各種アクティビティも含めた変換ルールの設定例の完全版は、Sharetaryのリポジトリに含まれていますので、参考にしてみて下さい。 ……というか、認証のための情報やfluent-plugin-groongaの接続先ホスト以外の全ての設定は、上記運用例でもこれをそのまま使っています。 皆さんも、試してみる時はこの設定をスタート地点として、ご自分のニーズに合うように微調整していくとよいでしょう。

ところで、上記の説明からも分かる通り、適切なイベント形式のレコードに変換さえできるのなら、Sharetaryの情報ソースはGitHubのアクティビティでなくても構いません。 この設定例は、イベント同士を関連付けたり、イベントの種類に応じてSharetaryの画面上での目立たせ方を変えたりといった事のサンプルにもなっていますので、他の情報ソースを使う場合の参考になるのではないでしょうか。

まとめ

以上、最近開発・公開したfluent-plugin-github-activitiesSharetaryという2つのソフトウェアの概要と使い方について簡単にご紹介しました。

この両者の組み合わせは利用形態の一例に過ぎないとはいえ、現時点では最も代表的な利用形態でもあります。 GitHubを開発の場所として利用するハッカソン形式のイベントを開催される際は、会場内での賑やかしとして、またイベント期間中の出来事を後から振り返る際のログとして、是非利用してみてください。

また、Sharetaryの運用例としてクリアコード関係者の公開アクティビティの定点観測所についてもご案内しました。 クリアコードが日常の業務の中でOSSやフリーソフトウェアを開発・公開したり既存プロジェクトに協力したりしている様子を俯瞰してご覧頂けますので、弊社オフィスに遊びに来られるような感覚でフラッと覗いてみて頂ければ幸いです。

2015-06-10

SEゼミ2015 - リーダブルコード勉強会を開催

2015-06-06にプログラミングが好きな学生のためのリーダブルコード勉強会を開催しました。この勉強会について、内容を作った立場からどうしてこのような内容にしたのかについて紹介します。また、今回の内容の課題と今後の解決案についてもまとめます。

基本的な流れは昨年開催したリーダブルコード勉強会と同じで次の通りです。

  1. 概要説明(「既存のコードからリーダブルなコードを発見する」を体験するよ!)
  2. 課題実装開始(各自リーダブルなコードで書く)
  3. コードチェンジ
  4. チェンジしたコードをベースに継続して課題を実装(リーダブルなコードを探しながらリーダブルなコードで書く)

コードをチェンジ(交換)して実装を継続することで「強制的に他の人の書いたコードを読む機会を作る」ことがポイントです。

詳細な流れに興味のある方はGitHubのclear-code/sezemi-2015の中に資料があるので、自由に活用してください。当日は6種類のスライドを使いましたが、それらへのリンクも含んでいます。資料もスライドもライセンスはCreative Commons BY-SA 4.0で、著作者は「株式会社クリアコード」です。

今年と昨年の違い

今年と昨年で基本的な部分は変わっていませんが、大きく変わったことがあります。それは次のことです。

  • メンターが増えた
  • テーマがOSSの開発になった
メンターが増えた

昨年はメンター5名で学生30名くらいでやっていましたが、今年はメンター20名超で学生50名くらいでやりました。メンターは単に数が増えただけではありません。優秀度合いは去年同様高いままです。メンターのリストを見れば「学生うらやましい!社会人だけど参加したかった!」と思うことでしょう。

メンターが増えたため、メンターそれぞれにどう振る舞って欲しいかを具体的に伝えることができなくなりました。そのため、今年は方針を共有して後はいい感じに振る舞ってもらうことにしました。共有した方針は「学生さんに新しい視点を与えること」です。答えを教えるのではなく、一緒に考えて、新しい視点を与えて欲しい、とお願いしました。

たとえば、ほとんどの学生さんは「他の人のコードからリーダブルなコードを探して自分のコードに取り入れる」ということを初めて経験します。新しいことに拒否反応を示したり、やることはわかってもうまくできないかもしれません。そんなときは、学生さんから今の気持ちを聞いて考えを整理することを手伝ったり、見方をこう変えてみるとなにか見えてこないか示唆を与えたりして欲しいということです。

難しいところもいろいろあったでしょうが、メンターのフォローにはとても助けられました。

たとえば、コードを読む習慣がない人の場合、プログラミングがうまければうまいほど、他の人のコードからうまくリーダブルなコードを見つけることができません。これは、「自分が書いている書き方がよい」という意識が強くなっているからです。こうなっていると、よほど自分よりうまく書かれていない限りリーダブルなコードを見つけられません。リーダブルなコードだと認識できない、と表現したほうが近いかもしれません。

このようなリーダブルなコードを見つけにくい状態になっている場合のフォローは難しいものです。「このコード、自分もやっているリーダブルな書き方じゃない?」とか、「違う見方で考えるとリーダブルだといえない?」というように、「すぐそばにある野生のリーダブルコード」に気づくきっかけを与えてくれたことでしょう。

リーダブルコードは読む人にとってわかりやすいコードなので、「キラキラしたリーダブルコード」を探そうと思うとかえって見つけられません。リーダブルコードはキラキラしていません。地味です。特別に意識せずともわかってしまうようなコードですから。そのようなコードを見つけて自分のプログラミングに取り入れるためには、「すぐそばにある野生のリーダブルコード」に気づける視点が必要なのです。

今回の勉強会では、学生さんには「すぐそばにある野生のリーダブルコード」に気づけるようになって欲しいと期待していました。そのため、メンターのみなさんにも「新しい視点を与える」という振る舞いをお願いしました。

テーマがOSSの開発になった

昨年のSEゼミのテーマは「インターンシップの準備」というテーマ(たぶん)で、リーダブルコード勉強会は「リーダブルコードを押さえておこう」ということで開催しました。

一方、今年のSEゼミのテーマは「OSSの開発に参加しよう!」で、リーダブルコード勉強会は「OSSの開発にリーダブルコードの知識があるとよい」ということで開催しました。

これを受けて、導入での説明も変えました。「他の人のコードを読んでよいところを取り込んで自分のコードに活かす」というのはOSSの開発では当たり前のこと。リーダブルコードを書けるようになるためにも同じやり方が使えるんだから、OSSの開発に参加するならこのやり方でいこう。という方向にしました。

これからOSSの開発に参加する中で、「他の人のコードから学ぶ」というやり方を当たり前の習慣として身につけてくれることを期待して説明を変えました。

今年から始めたこと

今年から「メビュー」を始めました。「レビュー」と似ていますが違います。

「メビュー」は「Mentor's View」の略で「メンターの視点」という気持ちです。

レビューは「問題がないか」という視点でコードを読みますが、メビューは「メンターの視点を共有する」という視点でコードを読みます。

自分だとよいと思っていなかったコードでも、メンターから見ればよいコードだと教えてもらえるかもしれません。リーダブルコードの解説にも次のように書いていますが、最初はヒントなしでよいコードを見つけることは難しいです。熟練したメンターの視点をもらって自分の「よいコードを見つける視点」を養います。それがメビューです。

実際にやるとぶつかること

まず、内容を活かす場所がぜんぜんないって思うだろう。でも、それはあなたの書いたコードがすでにリーダブルだからじゃない。単に「気づかない」からだ。本書を読んでいるときは次々と出てくる小さなコードを読みながら「たしかにここはそう変えた方がいいな。ふむふむ。」なんて読んでいただろう。読みながらあなたが「ふむふむ」言っていられたのは著者がわかりやすく書いてくれていたからだ。でも、実際のコードにはそういうヒントはない。著者のヒントがないんだから、実際のコードを読んでいるときは見逃してしまうだろう。それが自分が書いている真っ最中のコードならなおさらだ。読む側の視点じゃなくて書く側の視点でコードを見ているんだから。

課題と解決案

学生さんにはアンケートを書いてもらい、メンターの何人かには直接感想を聞きました。

アンケートを見る限り多くの学生さんにはよい機会となったようです。この勉強会をきっかけにOSSのコードを読んでいこうという気になった人もたくさんいました。

メンターの人の何人かには「楽しかった」と言ってもらえました。楽しくフォローしていたのなら、学生さんにもいろいろ伝わりやすかったことでしょう。

一方、次の点では課題がありました。

  • 最初の開発環境準備
  • プログラミング言語と課題のミスマッチ
  • 交換対象のプログラムの選び方

昨年もそうでしたが、何人かは開発を始める前のところでつまづきます。不安な人は30分くらい早めに会場に来てもらって、勉強会前に開発を始める準備のところを手伝ってあげるのがよいかもしれません。

この勉強会で実装する課題はファイルの読み込み処理があります。開いたファイルの管理方法、ファイルをどこに配置するか、などで工夫ポイントがでるかもしれないと考えて入れた処理です。しかし、SwiftやWebブラウザー上で動作するJavaScriptなどファイル操作をすることがまれなプログラミング言語では実装の難易度をあげてしまいます。これは意図したことではないのでファイルを使うこと自体やめた方がよさそうです。

途中でコードを交換することはこの勉強会での大事なところですが、交換する相手の実装がほとんど進んでいない場合は読む機会が少なくなってしまいます。その場合はあまり進んでいない人のコードは交換対象とせずに、ある程度進んでいる人のコードを2人以上で使うなどの交換の仕方の方がよさそうです。

他にも、課題から自由度を減らして実装に集中しやすくする、メモのまとめ場所をGitHubのIssueにしてメモをまとめるときの敷居をさげる、といったことも検討しています。

まとめ

昨年に引き続き開催したSEゼミのリーダブルコード勉強会の内容について、昨年の内容と比べながら紹介しました。また、実際に開催したわかった課題とその解決策もまとめました。

今年のSEゼミのイベントはまだ3つあります。引き続き優秀なメンターが多数参加するので、OSSの開発に参加したそうな学生さんに教えてあげてください。

今回の勉強会に参加した学生さん・メンターの感想に興味のある方は次のWebページを見てみてください。

今回の勉強会での成果に興味のある方は次のWebページを見てみてください。

2015-06-08

2015年6月6日(土)に学生向けリーダブルコード勉強会を開催予定

要約:6月6日(土)に学生向けリーダブルコード勉強会を開催する予定なので、興味のある学生の人はイベントページから応募してください。興味のありそうな学生を知っている人は教えてあげてください。締め切りは6月1日(月)です。

去年も開催した学生向けリーダブルコード勉強会を今年も開催します。(去年は参加者多数につき2回開催しました。1回目の開催レポート2回目の開催レポート。)今年も勉強会の講演者・トレーナーをリーダブルコードの解説を書いた須藤が務めます。

リーダブルコードはとても内容が評価されている本です。リーダブルコードが発売されたのは3年前ですが、今でも新しく読む人が増えています。例えば、2014年のジュンク堂書店池袋本店でのコンピュータ書売上冊数ランキングで1位*1になったり、Amazonのクラウドエンジニアが選ぶ技術書35選でも一番最初に紹介されていたりします。学生のうちからこの本に書かれていることを理解し、実践していると、今後、プログラミングをする上で大きな力になるでしょう。

内容

内容は去年と同じくリーダブルコードの解説に書いていることの体験です。解説では、本文に書いているリーダブルコードを書くための方法をどうやって実践するか、その方法を説明しています。つまり、学生がリーダブルコードに書いていることを単なる知識ではなく、実際に自分の技術や考え方として身につけるきっかけとなる内容です。

リーダブルコードを書くには読む人の視点を持っていることが重要です。リーダブルかどうかは読む人の視点での基準だからです。読む人の視点を持たずに書いているうちは、単に「想像して」リーダブルだろうコードを書いているだけです。読む人の視点を持っていれば、「事実に裏付けされた」リーダブルコードを書けます。

しかし、学生で読む人の視点を持っている人は少ないです。仕事では複数の人が同じソフトウェアを開発することが多く、また、リリースしたあとに継続して開発を続けることも多いので、コードを読む機会が増え、読む人の視点が身につきやすいです。一方、学生は自分ひとりで開発し、作って動かして研究成果が得られたらコードに触れることはない、というケースがほとんどです。そうすると、自分が書いたにしろ他の人が書いたにしろ、コードを読む機会がほとんどなく、読む人の視点を身につけることができません。

今回の勉強会ではこの「読む人の視点」の体験を重視します。具体的には次の流れで勉強会を進めることで、「読む人の視点」を体験します。

  1. 参加者全員にこの勉強会で開発するプログラムの仕様を伝える。
    • 参加者全員が同じプログラムを開発する。
    • プログラムの難易度は簡単なものにする*2
  2. 参加者全員が一定時間で各自プログラムを開発する。
    • 一定時間経過後、みんな途中まで開発を進めている状態にする。
  3. 参加者同士で開発しているプログラムを交換し、交換したプログラムを元に開発を継続する。
    • 開発を継続するためには交換したプログラム(= 他の人が書いたコード)を理解する必要がある。
      • 複数人での共同開発や作って終わりではない開発の疑似体験となり、コードを読む機会となる。これで「読む人の視点」を体験する。
      • 自分が書いたコードよりもリーダブルに書いているコードを見つけることを重視してもらう。ダメなところ探しに終始してグチグチ言っていたら勉強会が終わっていた、となったら失敗。
  4. 実際に開発をし、「読む人の視点」を体験してみて感じたことをまとめ、整理する
    • 「面白かった」、「難しかった」だけで終わらせず、「なぜ難しかったか」など理由まで落としこむ。理由まで落としこめれば、今回だけの体験で終わらず自分に身についた今後も活かせる財産になるはず。

「コードを読む」というと「コードレビュー」というイメージが根強いので、どうしても「悪いところはないか?」という視点で読んでしまいがちです。そうではなく、「コードから学ぶ」という視点で読めるようになると、コードの理解も速くなりますし、読んでいる人の成長スピードもあがります。日々新しい技術が生まれ、学ぶことが増えている昨今、コードから学べるようになると重宝されることでしょう。

参加する学生の人には「読む人の視点」、もっと言うと「悪いところはないか?」ではなく「コードから学ぶ」という「読む人の視点」を身につけ、コードを読み、よい書き方を身につけ、それを自分が書くコードにも活かす、という習慣を体験してもらいたいです。

メンター

去年はメンターとして企業で活躍中のトップエンジニアが学生をサポートしました。具体的には、クックパッドのまきもとさん、楽天の川原さん、DeNAのたなべさん、クリアコードの結城さん沖元さんがサポートしました。学生でなくてもサポートして欲しいですよね。

今年は去年よりもたくさんのトップエンジニアが学生をサポートする予定です*3。現在スポンサーになることが決定している企業は次の通りです。調整中の企業もあるのでまだ増える見込みです。

これらの企業からトップエンジニアがメンターとして参加し、学生さんをサポートしてくれます。例えば、クラウドワークスからはCTOの大場さんがメンターとして参加します。他にもメンターが決まったら随時イベントページに載るはずです。

まとめ

6月6日(土)に開催する学生向けリーダブルコード勉強会を紹介しました。リーダブルなコードを書くために大事な「読む人の視点をもつこと」を体験できる勉強会です。興味のある学生の人はイベントページから応募してください。興味のありそうな学生を知っている人はこの勉強会を教えてあげてください。あるいは、成長させたい学生を知っている人はこの勉強会への参加を勧めてください。

なお、締め切りは6月1日(月)です。

*1 2012年、2013年も1位なので、3年連続1位。しかも、2014年はその前の年より売れたみたいです。

*2 難しいプログラムを開発するためのテクニックを学ぶことが目的ではない。

*3 たぶん。

2015-05-13

6月24日開催 アジャイルアカデミー「実践リーダブルコード」募集開始のお知らせ

6/24(水)にアジャイルアカデミー「実践リーダブルコード」を開催することになりました。前回の3月6日に続いて、2回目の開催です。

「コードの読み手の視点」を身につける

「実践リーダブルコード」は、「リーダブルコードを書けるようになりたい人」や「リーダブルコードを書く開発チームにしたい人」が対象です。

ただし、このワークショップではリーダブルコードを書くためのテクニックは学びません。リーダブルコードを書くために必要な「コードの読み手の視点」を身につける事が、この講座の目標です。

リーダブルなコードかどうかはコードを読む側が判断するものですので、リーダブルなコードを書くためには、この「コードの読み手の視点」が不可欠です。この講座では「読む人が 読みやすいなら リーダブル」を合い言葉にして、今までとは違った角度からリーダブルコードへの理解を深めていきます。

「1年目から身につけたい! チーム開発6つの心得」にも関連

クリアコードでは、4/24発売WEB+DB PRESS Vol.86の特集記事「1年目から身につけたい! チーム開発6つの心得」を執筆しました。この記事は、チームによる開発をうまく回していくために不可欠な、「良いコードを書くこと」と「良いコミュニケーション」を実現するためのノウハウを紹介しています。

「実践リーダブルコード」で伝えたいことは、この記事に通じる内容です。「実践リーダブルコード」でも、「チーム開発6つ心得」で紹介するノウハウのいくつかを学ぶことができます。チーム開発6つの心得に取り組んでみようという方も、是非「実践リーダブルコード」の受講をご検討ください。

新人、ベテラン問わず学べる

実践リーダブルコードでは、開発チームでリーダブルコードを実践する方法として、「チームの『よい』を共有して、それを育てる」というやり方を学びます。これは「コーディング規約を作って守らせる」や「コードレビューで問題を指摘する」といったアプローチとは大きく異なりますので、開発経験豊富なベテランの方にとっても新鮮な学びになることでしょう。実際に、前回の受講者からもそのような声を多くいただきました。

まとめ

6/24に開催するアジャイルアカデミー「実践リーダブルコード」の募集を開始しましたので、講座の特徴を紹介しました。前回の内容はGitHubで公開していますので、ご検討の際は参考としてください。

ご興味をお持ちになられた方は、是非アジャイルアカデミーのページからお申し込みください。

2015-04-22

リーダブルコードの伝え方

4月3日にschooで名著『リーダブルコード』を解説者と一緒に読み解こう ~7章 制御フローを読みやすくする~というWeb授業をしました。資料と内容の概要は告知エントリーにあるので、興味のある方は参照してください。

授業の中で回答したもののうちよい回答をしたものがあったので紹介します。質問は「新人プログラマーにどうやってリーダブルコードについて伝えるか」です。授業の録画で言うと「Q:リーダブルコードについては初日から教えますか? 48:17」のところです。

みなさんは新人プログラマーにどうやってリーダブルコードについて伝えていますか?本を渡して「これ読むといいよ」ですか?「一緒にこの本を読もう」ですか?

回答は「自分がリーダブルコードを書くことによりリーダブルコードを伝える。本を読んでおけとも言わないし、そもそも『リーダブルコード』という用語も使わない。」でした。本を読んだり、「リーダブルコード」という用語があるという知識は、「リーダブルコードが当たり前」になってからで遅くありません。まずは、自分がリーダブルコードを書くことでリーダブルコードを伝え、リーダブルコードを当たり前にしてください。

実はこのことはリーダブルコードの解説にも書いています。より詳しいことは解説を読みなおしてみてください。

ちなみに、今回の授業は「受けたい数」が625人、「受講者数」は399人だったのですが、受講者のなかで「まだリーダブルコードを読んでいない」という人が8割ほどいました*1。おそらく、読んでいないけど「リーダブルコード」に興味があるという人が多いということです。このことから「リーダブルコード」という用語は普及していることがわかります。「リーダブルコード」の具体的な内容についてはまだ普及の余地がありそうです。

*1 正確に言うと、受講者のうち「リーダブルコードを読んだか」という質問に回答した人が29人で、そのうちの23人が「まだリーダブルコードを読んでいない」と回答しました。

2015-04-17

WEB+DB PRESS Vol.86の特集記事「1年目から身につけたい! チーム開発6つの心得」を執筆しました

2015年4月23日発売予定のWEB+DB PRESS Vol.86の特集記事を、クリアコードとして執筆しました。社内の多くの人間が関わった力作となっております。 書影はまだ登録されていないようですが、Amazon.co.jpでは既に注文できるようになっている模様です

WEB+DB PRESS Vol.86
結城 洋志/沖元 謙治/足永 拓郎/林 健太郎/大竹 智也/内田 誠悟/伊藤 直也/中山 裕司/hiroki.o/泉水 翔吾/佐藤 太一/高橋 俊幸/西尾 泰和/舘野 祐一/中島 聡/橋本 翔/はまちや2/竹原/麻植 泰輔/WEB+DB PRESS編集部
技術評論社
¥ 1,598

この特集記事では、「1年目から身につけたい! チーム開発6つの心得」と題して以下の6つの話題を扱っています。

  • 統一感のあるコード
  • 見通しの良い設計
  • わかりやすい名前
  • 伝わるコミット
  • コードを読む秘訣
  • 情報共有のコツ

「新人開発者などの初級者向けに、リーダブルコードやコードリーダー育成支援に関連した記事を」という事でご相談をいただきましたので、当初は、弊社メンバーそれぞれが持っている「初級者に伝えたい話題」を持ち寄って、それをそのまま記事にするという事を考えていました。しかし、実際に挙げてみるとリーダブルコードなどの実装面の事以外の話題も多数出てきた上に、そもそも「よいコード」の必要性をまだ感じていないという段階の人に対して「こう書くとよいコードになりますよ」とだけ語っても心に響かないのではないかという疑問も生じました。そこで、「多人数のチームでの開発を円滑に進める上で、よいコードにはこのようなメリットがある」という切り口で話題を選定し、新人が先輩に教わって一人前のメンバーに成長していくというストーリー仕立てで、全体をまとめることにしました。

プログラムは何らかの処理をするための物であり、みんなで共同で制作した成果物そのものです。しかし、それと同時に、チームメンバー間での意思疎通の媒体にもなります。何時間もかけてコードの説明をしなくても、コード自体が読みやすい物になっていれば、コードを読むだけで、それを書いた他のメンバーが込めたメッセージを読み取ることができるでしょう。また、Gitなどのバージョン管理システムにおける個々のコミットやその履歴(コミットログ)も同様で、きちんと考えたコミットを積み重ねれば、それらもまたメンバー間でのコミュニケーションの媒体として活用できます。

個々のテクニックを身に着けることや、個人技としての技術そのものに囚われないで、何のために工夫をするのかという観点を持つことを、この特集記事では大切にして執筆しました。チーム全体の開発効率や成果物の品質などの全体的な底上げに繋がる知識として、実際の現場でも役立てていただけるはずです(弊社内でも実践しています)ので、チーム内での連携の取り方でお悩みの方はぜひご一読ください。

なお、ページ数の制限や話題の繋がりなどの都合から掲載には至らなかった話題は、ここ(ククログ)で記事として順次公開していく予定です。実際に、一部は既に「クリアなコード」カテゴリの記事として公開済みです。記事本編の補足になるような話題もありますので、本誌と併せてこちらもぜひご覧下さい。

2015-04-08

2015年4月3日(金)にschooで2回目のリーダブルコードのWeb授業を開催予定

昨年、2014年12月9日にschooでリーダブルコードのWeb授業を開催しました。そのときは単発の企画で続編の予定はなかったのですが、今週の金曜日(2015年4月3日)の21:00から続編として名著『リーダブルコード』を解説者と一緒に読み解こう ~7章 制御フローを読みやすくする~の授業をすることになりました。

内容

前回は「3章 誤解されない名前」をベースにした授業でしたが、今回は「7章 制御フローを読みやすくする」をベースにした授業です。前回受講した人はわかると思いますが、本の内容をベースにした「初級者向け」の内容です。一部、ステップアップしたい人向けの内容も含まれていますが、基本的に本の内容をベースにしています。

本の内容を十分理解したという方は受講しなくても大丈夫です。

本は読んだけどピンときていない、リーダブルコードに詳しい人に質問したい、他の人のリーダブルコードに対する意見を聞いてみたい、という方は受講してみてください。

授業で使う資料は次の通りです。受講するかどうかの参考にしてください。

リーダブルコードを書くためにはコードを読むこと

資料でも触れていますが、リーダブルコードを書くために大事なことは「コードを読むこと」です。受講しない人も、「リーダブルコードを書きたいならコードを読むこと」ということは覚えておいてください。コードを読むことで読む人の視点がわかりますし、リーダブルなコードの書き方を知ることもできます。必ず役に立ちます。

この話をすると「コードを読めばよいと言うけどどんなコードを読めばよいのだろう」となる人が結構います。そんな人は「自分が使っているオープンソースソフトウェアのコード」を読んでください。

どうして自分が使っているオープンソースソフトウェアがよいのかというと、使い方やどう動くかがわかっているからです。動きがわかっていると、動きを対応させてコードを読むことができるので理解しやすくなります。読む人の経験を養えるだけでなく、実はこんな使い方もできたのか、こんな機能もあったのかと新しい発見もあるかもしれません。自分が使っているソフトウェアのソースだと楽しくコードを読めるはずです。

ぜひ、「自分が使っているソフトウェアのコードを読んでみよう」という観点でコードを選んで読んでみてください。

まとめ

それでは、本は読んだけどピンときていない、リーダブルコードに詳しい人に質問したい、他の人のリーダブルコードに対する意見を聞いてみたい、という方は授業でお会いしましょう。そうでない方もコードを読むことを当たり前にして、よりリーダブルなコードを書けるようになってください。

2015-03-31

Git でコミットメールを配信する方法

クリアコードが開発に関わっているプロジェクトではdiff付きのコミットメールを流すようにしています。

コミットメールを流すための方法を紹介します*1

使用しているのは以下の2つのGemです。

環境はDebian GNU/Linux wheezyを想定しています。Rubyのバージョンは1.9.3以降であれば問題ありません。

専用のユーザを用意します。

$ sudo useradd app

アプリケーションをデプロイするディレクトリを用意します。

$ sudo mkdir -p /srv/www/webapps/web-hooks-receiver
$ sudo chwon -R app:app /srv/www/webapps

/srv/www/webapps/web-hooks-receiver/Gemfileを用意します。

1
2
3
4
source "https://rubygems.org"

gem "git-commit-mailer"
gem "github-web-hooks-receiver"

ここではApache+Passengerでセットアップします*3

以下のコマンドを実行し、必要なGemをインストールします。

$ cd /srv/www/webapps/web-hooks-receiver
$ sudo -u app -H bundle install --binstubs --path vendor/bundle --without=development,test

ここで--binstubsオプションを付けているのは、カレントディレクトリのbin以下にgit-commit-mailerコマンドをインストールするためです。

Rackアプリケーションとして動作させるための設定ファイルconfig.ruを用意します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require "yaml"
require "pathname"
require "github-web-hooks-receiver"

use Rack::CommonLogger
use Rack::Runtime
use Rack::ContentLength

base_dir = Pathname(__FILE__).realpath.dirname
config_file = base_dir + "config.yaml"
options = YAML.load_file(config_file.to_s)

map "/post-receiver/" do
  run GitHubWebHooksReceiver::App.new(options)
end

もし、GitHubWebHooksReceiver::Appの実行中に発生したエラーをメールで通知したい場合は、exception_notificationrackngaを使うと良いでしょう。

Passenger の設定は以下のようにします*4

/etc/apache2/sites-available/github-web-hooks-receiver:

<VirtualHost *:80>
  ServerName web-hooks-receiver.example.com
  DocumentRoot /srv/www/webapps/web-hooks-receiver/public
  <Directory /srv/www/webapps/web-hooks-receiver/public>
     AllowOverride all
     Options -MultiViews
  </Directory>

  ErrorLog ${APACHE_LOG_DIR}/web-hooks-receiver_error.log
  CustomLog ${APACHE_LOG_DIR}/web-hooks-receiver_access.log combined

  AllowEncodedSlashes On
  AcceptPathInfo On
</VirtualHost>

/etc/apache2/mods-available/passenger.conf:

PassengerRoot /path/to/passenger
PassengerRuby /path/to/ruby

/etc/apache2/mods-available/passenger.load:

LoadModule passenger_module /path/to/mod_passenger.so

GitHubWebHooksReceiver用の設定ファイルconfig.yamlを用意します*5

base_dir: /srv/www/webapps/web-hooks-receiver
mirrors_directory: mirrors
git_commit_mailer: bin/git-commit-mailer
to: 
  - commit+default@example.com
error_to: commit+error@example.com
exception_notifier:
  subject_label: "[git-commit-mailer]"
sender: null+git-commit-mailer@example.com
add_html: false
domains:
  github.com:
    owners:
      clear-code:
        to: commit@example.com
        from: null+github@example.com
        repositories:
          cutter:
            to:
              - cutter-commit@lists.sourceforge.net
          cutter-macports:
            to:
              - cutter-commit@lists.sourceforge.net
          pikzie:
            to:
              - pikzie-commit@lists.sourceforge.net
      rabbit-shocker:
        to: commit@ml.rabbit-shocker.org
        from: null@rabbit-shocker.org
  gitlab.example.com:
    to: commit+internal@example.com
    from: null+gitlab@example.com
base_dir
基準となるディレクトリを絶対パスで指定します。
mirrors_directory
Gitリポジトリをミラーするディレクトリをbase_dir からの相対パス、または、絶対パスで指定します。 省略すると#{base_dir}/mirrorsを使用します。
git_commit_mailer
git-commit-mailer コマンドへのパスを指定します。 ruby コマンドの引数として実行するので、任意の Ruby スクリプトでも構いません。
error_to
エラーが発生したときの宛先を指定します。
to
コミットメールの宛先を配列で指定します。
sender
送信者のメールアドレスを指定します。 Sender ヘッダーの値や from が指定されなかった場合のエンベロープFromの値として使用します。
from
省略すると Author の name と email から「Commiter Name <commiter@example.com>」のようなメールアドレスを生成して使用します。指定可能なのはメールアドレスの部分のみです。
add_html
true を指定すると、HTMLメールで色付きのdiffを生成します。
send_per_to
true を指定するとtoごとに別のSMTPセッションでコミットメールを送信します。
sleep_per_mail
コミットメールを一通送信するごとに指定された秒数だけsleepします。

コミットメールの宛先を指定する方法

以下の4つの単位で指定することができます。マッチした条件のうち最も小さい範囲にコミットメールを送信します。

  • 全体
  • ドメイン単位
  • オーナー単位
  • リポジトリ単位

上の例で言うと、以下のように配信します。

  • github.com の clear-code organization にある cutter, cutter-macports リポジトリは cutter-commit@lists.sourceforge.net にコミットメールを配信する。その際 From ヘッダーの値には Author の name と email を使用する。
  • github.com の clear-code organization にある pikzie リポジトリは pikzie-commit@lists.sourceforge.net にコミットメールを配信する。その際 From ヘッダーの値には Author の name と email を使用する。
  • github.com の clear-code organization にある cutter, cutter-macports, pikzie 以外のリポジトリは commit@example.com にコミットメールを配信する。その際 From ヘッダーの値には Author の name と null+github@example.com を使用する。
  • github.com の rabbit-shocker organization の全てのリポジトリは commit@ml.rabbit-shocker.org へコミットメールを配信する。その際 From ヘッダーの値は Author の name と null@rabbit-shocker.org を使用する。
  • gitlab.example.com の全てのリポジトリは commit+internal@example.com へコミットメールを配信する。その際 From ヘッダーの値には Author の name と null+gitlab@example.com を使用する。
  • 上記全ての条件にマッチしないリポジトリは commit+default@example.com へコミットメールを配信する。その際 From ヘッダーの値には Author の name と email を使用する。

プライベートリポジトリのコミットメールを流す場合

GitHubやGH:Eの場合は、検証していないのでわかりません。検証した人はこっそり教えてください。

GitLab の場合は、全てのリポジトリにReporter権限のユーザーを追加して、そのユーザーにパスフレーズなしの公開鍵を登録します。その上で以下のコマンドを一度実行して $HOME/.ssh/known_hosts に情報を登録しておきます。

$ ssh -T git@gitlab.example.com

この作業を忘れると、コミットメール配信用のリポジトリをミラーできません。

予告

GitLab でリポジトリを追加するたびに手動でWeb hookを追加するのは面倒です。 そこで、自動でWeb hookを登録できるWeb hook*6としてgitlab-system-hooks-receiverを作りました。

またGitHubで自分の管理下にないリポジトリのdiff付きのコミットメールを読みたくなることがあると思います。GitHubのフィードだとdiffが付いていないため、diffの確認に一手間かかってしまいます。github-event-watcherを使うと自分の管理下にないリポジトリでもdiff付きのコミットメールを配信することができます。

次回はそれらの使い方を紹介します。

まとめ

GitHubやGitLabにあるリポジトリのコミットを色付きのdiffを含むコミットメールで通知する方法を紹介しました。

*1 元々 git-utilsとして開発していたものですが、より使いやすくするためにGemとして公開しました。

*2 GitLabやGH:Eも対応しています

*3 他のアプリケーションサーバを使うときは適宜読み替えてください。

*4 Passengerの設定方法はpassenger-install-apache2-moduleコマンドを実行したときにも表示されます。

*5 クリアコードで使用しているものをアレンジしたものです。

*6 GitLabではSystem hookと呼び通常のWeb hookとは区別しています。

2015-03-27

タグ:
年・日ごとに見る
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|