GitBucket 4.31.0、4.31.1をリリースしました

Scalaで実装されたオープンソースのGitサーバ、GitBucket 4.31.0および4.31.1をリリースしました。

4.31.0では多くの新機能が利用可能ですが、すでにバグ修正版である4.31.1がリリースされていますので、こちらを利用することをお勧めします。

CIプラグインでDockerをサポート

gitbucket-ci-plugindockerdocker-composeがサポートされました。

f:id:takezoe:20190317114732p:plain

f:id:takezoe:20190317114753p:plain

管理者が有効にすることで各リポジトリのビルド設定でビルドに使用するDockerfileもしくがdocker-compose.ymlを選択できるようになります。なお、この機能を利用するにはGitBucketが稼働しているマシン上にdockerがインストールされている必要があります。

GPG署名済みコミットの検証

GPG署名済みコミットの検証結果をコミット一覧画面に表示するようになりました。

f:id:takezoe:20190317114809p:plain

Web APIの強化

OAuth2 Token (sent as a parameter)認証がサポートされました。また、以下のAPIが新たに追加されました。

OGPのサポート

Open Graph protocolで定義されたmetaタグ(og:title, og:imageなど)を出力するようになりました。ブログやソーシャルメディア、チャットなどでGitBucketのURLを共有する際に便利です。

ユーザ名補完にアバターも表示

ユーザ名の補完時にユーザ名だけでなくアバターを表示するようになりました。

f:id:takezoe:20190317114845p:plain

今回のバージョンではこの他にもMySQL8のサポートなどの改善や、様々なバグフィックスを行っています。詳細については4.31.0でクローズされたIssueの一覧および4.31.1でクローズされたIssueの一覧をご覧ください。

Cherry MX ロープロファイル赤軸キーボード「Majestouch Stingray」を買ってみた

以前購入した青軸ロープロファイルスイッチのキーボードが思いの外具合が良かったので、これの赤軸版があればなぁ…と思っていたところFILCOからCherry MXのロープロファイルスイッチを使ったキーボードが発売されたとのことで思わず購入してしまいました。

実際に試してみたところ、もっとスコスコした感じを想像していたのですが、思っていたより重いキータッチだなという印象です。キーキャップはマットな感じで筐体もしっかりしているので高級感はあります。自分が購入したのはテンキーレスモデルですが、それでも重量がかなりあるので持ち運びには向いていません。また、キートップを外すための工具や交換用のキートップPS2用のアダプタ(!)が付属しています。

f:id:takezoe:20190307011313j:plain

自宅ではデスクが狭いこともありHHKBやThinkPadキーボードのようなコンパクトキーボードをずっと使っていたのですが、このくらいの配列の方がFnキーとのコンビネーションを使う必要もありませんし、実はThinkPadキーボードのトラックポイントが意外と肩凝りの原因になっているのではという疑惑もあるのでしばらくこのキーボードを使ってみようと思います。

キーボードを買ってはトラックポイントの便利さに抗えずThinkPadキーボードに戻るという行為を繰り返しているのですが、果たして今回は使い続けられるでしょうか…。

Treasure Data hosted Tokyo Scala Developers Meetup!

f:id:takezoe:20190303134341j:plain

2月28日(木)に大手町のGlobal Business Hub Tokyoで開催されたTokyo Scala Developersコミュニティのミートアップをトレジャーデータでホストさせていただきました。

www.meetup.com

Tokyo Scala Developersは主に東京在住の外国人Scalaプログラマのコミュニティで、主催者のGabrielさんがTwitterで開催場所を探しているのを見かけてトレジャーデータでの会場提供を提案させていただきました。

当日はあいにくの雨天でしたが20名以上の方に参加いただきました。トークの前に弊チームの@taroleoさんからトレジャーデータや、トレジャーデータのメンバーが開発しているScalaOSSの紹介をさせていただきました。

f:id:takezoe:20190303132429j:plain

発表者はお二方ともPaidyさんの方とのこと。まずはValentinさんによる「Literally type magic」というトーク

f:id:takezoe:20190303132457j:plain

歓談タイムを挟んでGabrielさんによる「Haskell for Scala developers」。ScalaHaskellで同じことをするコードを比較されていました。

f:id:takezoe:20190303132528j:plain

運営がややバタついてしまった部分もあり、私自身はあまりトークを聞いている余裕がなかったのですが、参加者の皆さんにお楽しみいただけたのであれば幸いです。また、個人的に久しぶりにお会いできた方もおり、近況を伺うことができてよかったです。

今回はトレジャーデータとして会場提供させていただきましたが、東京で参加できるScalaの英語コミュニティということで、次回以降も都合がつけば是非参加したいと思います。(発表者も求むとのことです!)

トレジャーデータのマウンテンビューオフィスで勤務してみた

f:id:takezoe:20190302011613j:plain

トレジャーデータはマウンテンビューに本社があり、東京とマウンテンビューのオフィス以外にも世界中に分散したチームを持っています。

私の所属するチームはTech Leadの@taroleoさんがマウンテンビューオフィスで勤務しているということもあり、オンボーディングの一環として私も2週間マウンテンビューのオフィスで勤務させてもらうことになりました(通常は入社3ヶ月以内に本社に行くケースが多いのですが、昨年は中々都合がつかず、入社から約半年経ってからの渡米となりました)。

到着直後はジェットラグが酷く、2日ほどはまるで使い物にならない感じでしたが、3日目くらいからは夜もちゃんと眠れるようになり、まともに仕事ができる状態になってきました。1週間の滞在だと落ち着いて仕事をするには短すぎるので、2週間はちょうどいい期間なのではと思います。オフィスから車で10分ほどのアパートメントに滞在し、毎日Uber通勤だったので通勤によるストレスがほぼゼロだったのは有難かったです。

こちらのオフィスは東京のオフィスと比べるとべらぼうに広く、空間的にだいぶ余裕がありました。デスクはスタンディングにもできる電動昇降式のものが導入されており、座って作業する場合でも高さを微調整できてなかなか良いものでした。

ランチにはEAT Clubというデリバリーサービス(毎日自分の食べたいメニューをWebサイトで選んでオーダーしておくとオフィスに届けてくれる)が導入されている他、ドリンクやアメリカンなスナックなどが無料で提供されており、とりあえず食べるものに困ることはありませんでした。一方で宿泊していたアパートメントの周囲には大きなスーパーマーケットなどがなく、買い物には結構困りました。夕食はオフィスからカップ麺を持ち帰って食べたりもしていましたw

f:id:takezoe:20190302011721j:plain

さて、肝心の仕事に関してですが、オンサイトで@taroleoさんと作業することができ、これまでずっと悩んでいたことが一発で解決したり、私の滞在とタイミングをあわせて渡米してきてくれたチームメイトも交えて3人でハッカソン的に開発をしたりと、同じロケーションで作業するメリットを大いに実感しました。

リモートワークや分散チームには採用対象の拡大、サポートや運用のタイムゾーンを分散できるなどのメリットもあるのですが、やはりオーバーヘッドも大きいと感じます。私の場合は英語力的な問題でリモートでのコミュニケーションに難しさを感じることも多いため、なおさらオンサイトで作業する機会の重要性を感じました。

また、マウンテンビュー滞在中に偶然にも前々職の同僚と2人も会うことができたのは嬉しい驚きでした。お二方とも数年ぶりの再会がまさかシリコンバレーとは予想だにしていなかったので世の中本当に狭いものだなぁと思いました。昔話をしつつ、現在や未来の話もしつつ、楽しいひと時を過ごさせていただきました。

これまでの海外滞在はカンファレンスへの参加など長くても一週間弱だったので、出発前は「二週間は長いなぁ」と思っていたのですが、終わってみればあっという間、短期間ではありましたが実りの多いマウンテンビュー勤務の二週間でした。東京に戻って心機一転頑張っていきたいと思います。

Scala用のRisonパーサを作ってみた

RisonというのはJSONライクかつURLに埋め込みやすいようURLエンコーディングが最小限になるよう設計されたデータフォーマットだそうで、Kibanaなどで使われているそうです。日本語だと以下の記事が詳しいです(自分もこの記事を見てRisonを知りました)。

qiita.com

すでにJavaScriptPythonGolangなど様々な言語向けのライブラリが存在するようですが、Scala用のライブラリが見当たらなかったので久しぶりにScalaのパーサコンビネータライブラリを使って実装してみました。

github.com

こんな感じで使います。

import com.github.takezoe.rison._

val parser = new RisonParser()

// parse
parser.parse("(name:Lacazette,age:27)") match {
  case Right(node) => println(node.toScala) // => Map(name -> Lacazette, age -> 27)
  case Left(error) => println(error)
}

// convert from Scala's Map
val node: RisonNode = RisonNode.fromScala(
  Map(
    "name" -> "Alexandre Lacazette", 
    "twitter" -> "@LacazetteAlex"
  )
)
println(node.toRisonString) // => (name:'Alexandre Lacazette',twitter:'@LacazetteAlex')

// URL encode
val encoded: String = node.toUrlEncodedString
println(encoded) // => (name:'Alexandre+Lacazette',twitter:'@LacazetteAlex')

O-risonやA-risonにも対応しています。

// O-rison
val orison: ObjectNode = parser.parseObject("name:Lacazette,age:27")
println(orison.toObjectString) // => name:Lacazette,age:27

// A-rison
val arison: ArrayNode = parser.parseArray("Lacazette,Aubameyang,Ozil")
println(arison.toArrayString) // => Lacazette,Aubameyang,Ozil

上記のサンプルではMapSeqを使っていますが、ケースクラスを使うこともできます。

// case class
case class Player(name: String, age: Int)

// convert from case class
val node: RisonNode = RisonNode.fromScala(Player("Lacazette", 27))
println(node.toRisonString) // => (name:Lacazette',age:27)


// convert to case class
val obj: Player = node.to[Player]
println(node.) // => Player(Lacazette, 27)

ケースクラスとの相互変換にはairframe-surfaceを使いました。

airframe-surfaceは型情報を取得してごにょごにょするためのScalaライブラリで、リフレクション関連の面倒な処理を隠蔽してくれます。Scalaだと同様の処理にはShapelessのようなライブラリを使う方法もありますが、airframe-surfaceはリフレクションならではの制限はあるものの気軽に使えて便利です。

このRisonパーサは冬休み中に作っていたのですが、パーサコンビネータやairframe-surfaceなどいろいろ試すことができてなかなか良い題材でした。

Java/Scala用機械学習ライブラリ「Smile」を使ってみる

手軽に使える機械学習ライブラリというとPythonのscikit-learnが有名ですが、Java/ScalaでもSmileというライブラリがあったので軽く試してみました。

github.com

まずはリリースページからzipファイルをダウンロードして適当な場所に展開します。bin/smileで対話シェルが起動します。

f:id:takezoe:20190104102332p:plain

サンプルデータがついているのでこれを使って学習してみます。

smile> val toy = read.table("data/classification/toy/toy-train.txt", response = Some(new NominalAttribute("class"), 0))
smile> val (x, y) = toy.unzipInt
smile> val model = knn(x, y, 3)

作ったモデルを使って予測してみます。

smile> model.predict(x(0))
res3: Int = 0

学習データとモデルをプロットしてみます。別ウィンドウが立ち上がって以下のようなグラフが表示されるはずです。SmileはJupyterでも使うことができるようですが、その場合はちゃんとノートブックにグラフが表示されるのだろうか…。

smile> plot(x, y, model)

f:id:takezoe:20190104103334p:plain

テストデータを使って精度を確かめてみます。

smile> val test = read.table("data/classification/toy/toy-test.txt", response = Some(new NominalAttribute("class"), 0))
smile> val (testx, testy) = test.unzipInt
smile> val pred = testx.map(model.predict(_))
smile> accuracy(testy, pred)
res9: Double = 0.81205

モデルの保存、読み込みはJava標準のバイナリシリアライゼーションで行われるようです。Xstreamを使ってXML形式で保存する機能も用意されているようです。読み込むときはちゃんとキャストしてあげないとAnyRef型になってしまうので注意が必要です。

smile> write(model, "toy.model")
smile> val model = read("toy.model").asInstanceOf[KNN[Array[Double]]]

データ加工にScalaのコレクションAPIを使えるのは便利ですが、もうちょっと高度なライブラリが欲しいかなという気もします。その辺は別のライブラリを組み合わせればよいのでしょう。以下のような感じで外部ライブラリが使えるみたいです。

smile> import $ivy.`org.scalaz::scalaz-core:7.2.7`, scalaz._, Scalaz._

思っていたよりサクッと使えましたし、対話シェルやグラフの描画などもよくできていました。Java/Scalaアプリケーションにちょっとした機械学習を利用した機能を組み込みたい場合には便利に使えるのではないかと思います。

Scalaのテストケース内でDockerコンテナを使う

GitBucketではもともとデータベースを使ったテストに組み込みMySQLやPostgreSQLの機能を提供するライブラリを使っていたのですが、これらのライブラリのメンテナンスが怪しくなかなか新しいバージョンに対応したテストを行うことができないという問題があり、Dockerを使えないかなと思っていたところ、以下のようなライブラリを教えてもらいました。

github.com

プログラム中からDockerコンテナを制御するためのライブラリで、Scala版の他にもJava版やGo版など様々な言語向けのライブラリが提供されています(Scala版はJava版のラッパーのようです)。

デフォルトでよく使うコンテナ向けの実装が提供されているのが特徴で、たとえばMySQLを使う場合、まずは以下の依存関係をbuild.sbtに追加します。

libraryDependencies ++= Seq(
  "com.dimafeng" %% "testcontainers-scala" % "0.22.0" % "test",
  "org.testcontainers" % "mysql" % "1.10.3" % "test"
)

以下のような感じでコンテナを使ったテストを簡単に行うことができます。JDBC URLやユーザ名、パスワードをコンテナから取得できるのが地味に便利です。

import com.dimafeng.testcontainers.{ForAllTestContainer, MySQLContainer}

class MysqlSpec extends FlatSpec with ForAllTestContainer {

  override val container = MySQLContainer()

  it should "do something" in {
    Class.forName(container.driverClassName)
    val connection = DriverManager.getConnection(
      container.jdbcUrl, container.username, container.password)
    ...
  }
}

複数のコンテナを使いたい場合はMultipleContainersを使います。

val mySqlContainer = MySQLContainer()
val postgresContainer = PostgreSQLContainer()

override val container = MultipleContainers(
  mySqlContainer, postgresContainer)

また、DockerComposeContainerでdocker-compose.ymlを使ってコンテナを起動することもできるようです。

override val container = DockerComposeContainer(
  new File("src/test/resources/docker-compose.yml"))

ちなみにデフォルトで用意されていないコンテナを使いたい場合はGenericContainerあたりを継承して自分で作成します。JDBC経由でアクセスするデータベースであればJdbcDatabaseContainerを継承するのが簡単です。

コンテナの起動をForAllTestContainerで制御するのではなく、コード中で直接制御することもできます。

val container = new MySQLContainer()
container.starting()
...
container.finished()

GitBucketでは特定のテストケースで複数の種類・バージョンのデータベースでテストを行う必要があったので、以下のような感じでテスト内で必要に応じて対象バージョンのコンテナを起動するような使い方をしています。

Seq("11", "10").foreach { tag =>
  test(s"Migration PostgreSQL $tag") {
    val container = PostgreSQLContainer(s"postgres:$tag")
    container.starting()
    try {
      // ここでテスト
    } finally {
      container.finished()
    }
  }
}

簡単に使える上で拡張性もあってなかなか便利なライブラリですね。

なお、同種のライブラリとして以下のようなものもあるようです。こちらでも同じようなことができそうですが、プログラム中からコンテナ単体での制御を行うことはあまり考えられていなそうな印象です。

github.com