Config::ENV で各環境で必ず定義すべきキーを定めておく

metacpan.org

ちょうどいいデフォルト値が見当たらないので、このキーは各環境で適宜、定義せよ、という風にしたい。interface みたいなかんじ。

use strict;
use warnings;
use Test::More tests => 2;
use Test::Fatal qw(exception);

{
  package My::Config::NotImplemented;

  use strict;
  use warnings;
  use Scalar::Util qw(blessed);

  sub new {
    my ($class) = @_;
    return bless {}, $class;
  }

  sub equals {
    my ($self, $other) = @_;
    return blessed($other) && $other->isa(ref($self));
  }
};

{
  package My::Config;
  use strict;
  use warnings;
  use Config::ENV 'MY_ENV';

  use constant NOT_IMPLEMENTED => My::Config::NotImplemented->new;

  sub declare ($) {
    my ($name) = @_;
    return ($name => NOT_IMPLEMENTED);
  }

  common +{
    declare 'log.path',
  };

  config production => {
      'log.path' => '/var/log/app.log',
  };

  config test => +{};
};

{
  package Test::My::Config;
  use strict;
  use warnings;
  use Config::ENV ();

  sub import {
    my ($class) = @_;
    no strict 'refs';
    no warnings 'once';
    *My::Config::param = \&param;
  }

  sub param {
    my ($class, $name) = @_;
    my $value = Config::ENV::param($class, $name);
    return !My::Config::NOT_IMPLEMENTED->equals($value);
  }
};

Test::My::Config->import;

my $envs = do {
  my $envs = My::Config->_data->{envs};
  [ keys %$envs ];
};

for my $env (@$envs) {
  local $ENV{MY_ENV} = $env;
  my $current = My::Config->current;
  for my $key (keys %$current) {
    ok +My::Config->param($key), "Environment ($env) defines `$key'";
  }
}

declare($name) で各環境で定義すべきキーを宣言する。これは対応する値として意味のないオブジェクトを便宜上定義する (Config::ENV は設定値として hashref を期待するので)。

テスト時に Config::ENV を継承したクラスの param($name) を上書きして、値が NOT_IMPLEMENTED オブジェクトかどうかをアサートする、という風にする。

すると # Failed test 'Environment (test) defines `log.path'' という風にテストが失敗し、たしかに test で log.path が定義されていない、ということがわかる。

JSON schema とか XML とか使うともっとリッチな検証ができそうだけど、Config::ENV でも素朴にできるというコンセプト。

#yapcasia に参加した

トークしつつ、トークを聞いたりなどした。

1日目

いろいろ聞いたけど印象的だったトークについて。

yapcasia.org

kazuho さんの発表を初めて見た。声が高い。

僕は大学を中退していてアカデミアどころか学部の卒業論文がどういう雰囲気なのかよく知らないけれども、論文やベンチマークなど根拠となるデータが必ずあってそれを軸に話がなされるので、けっこう速いペースにも関わらずすんなり聞くことができた。

アセットコンパイルを行う動機はリクエスト数を減らすだけではないので必ずしも HTTP/2 によってアセットコンパイルという考え方が無用の長物になることはないだろうと思った *1。とはいえ、言及されたように考え方を変える必要はあるだろうと思う。

言ってしまえば高々60分に満たない時間で HTTP/2 について一本筋の通った理解が得られたのは、事前に断片的な知識が自分にあったことを差し引いてもすごいことだな、と思った。

また、発表の内容自体もさることながら、自分の中で「わかりやすい」「聞きやすい」プレゼンテーションの具体例がひとつできたのも尊い。

yapcasia.org

WebAudio と信号入門の話。

「今まで Web エンジニアはディスプレイという限られた光情報しか操ることしかできなかったが、WebAudio によって空気を振動させディスプレイを越えて影響を及ぼすことができる」という風なことをおっしゃっていた。

少しおどけた風だったような気がするけれども、実際、胸を打たれた。僕はわりと音楽が好きで音が出ると喜ぶので WebAudio が Web API に入るときはおもしろそうだ、と思ったけれどもそれ以上のことは考えられなかった。

音波とか音響に関する知識はわずかながらあったけれども、信号の話は不明だったのでとてもおもしろかった。とてもエンターテイナーだなあ、と感じた。

yapcasia.org

2日目

yapcasia.org

タイトルにある通り Go におけるプロファイリングおよび最適化の話で、それはそれでもちろん参考になったけれども、思いがけずライブコーディングを見ることができてとてもよかった。

ごく個人的な感想

2012年にチケットを買っていて行くつもりだったけれどもぎりぎりでやめて、別の人に譲った。2014年も参加しなかった。どちらの年もトークを応募しなかったから。

自分はエンジニアで普段からインターネットなどを通じて情報を得たり OSS コミュニティからソフトウェア資産を享受し (つつ、わずかながら還元し) ており、では YAPC のようなカンファレンスで自分が果たせる役目とはなんなのかと問うてみると、おもしろトークをすることだと思っている。

また、そうやってトークをするという形で還元することをイベントに参加する条件として課しているのは「お客様気分」を持たせないためでもある。YAPC は有料のイベントなのでなおさらである。

YAPC::Asia Tokyo にて2回スピーカーとして登壇できたことはとても褒まれ高いことだと思っている一方で、YAPC::Asia Tokyo に区切りがついたとしてもそういったコミュニティへの還元への考え方は変わることはないだろう。

おもしろトークのために技術を磨いていかねば。

みなさまお疲れさまでした。

*1: たとえば browserify などはブラウザにおける JS 実行環境においてモジュールシステムがないことに対するアプローチである

#yapcasia で『世界展開する大規模ウェブサービスのデプロイを支える技術』という発表をした

世界展開する大規模ウェブサービスのデプロイを支える技術 - YAPC::Asia Tokyo 2015

2年ぶり2度目の YAPC::Asia Tokyo でタイトルにある通り発表をしてきました。資料は近日公開予定です。

立ち見続出のようで満員御礼といった具合で非常に嬉しいしありがたいことです *1

懇親会などで「おもしろかった」と声をかけてもらえることが多くて非常に嬉しかったです。 「おもしろかった」「最高」と思ったらぜひ ベストトークに投票 してもらえると嬉しいです!!!

*1: もっとも立ち見されていた方は60分近いトークだったので大変だったでしょうが

YAPC::Asia Tokyo 2015 のトークスケジュールを集めたカレンダーを作った

https://www.google.com/calendar/ical/037qm3orvt1bkf5g2lsi1br0bg%40group.calendar.google.com/public/basic.ics

YAPC::Asia Tokyo 2015 も来週に控えて、どういうトークを見にいくか悩むこの頃なのでカレンダーをつくりました。

yapcasia.org

トークリストから素朴にスクレイピングして作ってあります。organizer にスピーカーの名前が入れられるとよかったのですが、メールアドレスが必須で手頃に入手できそうになかったので諦めてイベントのサマリに入れてあります。

Google Calendar などにインポートするなどして、どうぞご利用ください。

npm install -g gulp とかしない流儀

gulp とかを使っているプロジェクトの場合、ビルドツール類も devDependencies に含めてバージョンを固定したいという要求があると思う。

ところが実行ファイルにパスを手軽に通したいという理由のみで npm install -g gulp などしてしまうとバージョンが固定できなくなってしまい、本末転倒である。

とはいえ ./node_modules/.bin/gulp build してくれ、というのも面倒であるので、どうするとよいのか書いておく。

node_modules/.bin に自動でパスを通す

たとえば direnv を使う。

# .envrc
export PATH="$(npm bin):$PATH"
direnv allow

.envrc は作業ディレクトリを移動した時に評価されるので、npm bin の出力が異なる環境でもそれぞれうまく動く (はず) なのでリポジトリに含めてもよい。

package.json の scripts フィールドを使う

package.jsonscripts フィールドに定義したタスクは npm run NAME で実行できる。

また、この scripts フィールドに定義したタスクの実行時には node_modules/.bin にパスを通した上で実行される。

In addition to the shell's pre-existing PATH, npm run adds node_modules/.bin to the PATH provided to scripts.

run-script | npm Documentation

統一されたインターフェースを用意するという意味でも scripts フィールドは望ましい。

結論

direnv と scripts フィールドは役割が少し異なるので、どちらか一方のみを利用するということもなく両方活用できるとよさそうですね。

#小学生のときにやってた悪夢っぽいこと選手権

スコアメーカーという楽譜入力ソフトがあってこれを買ってもらって遊びはじめたところ、MIDI というもので勝手にコンピュータに演奏させることができるとわかって大喜びしはじめた。

ひたすら入力して演奏させる。アーティキュレーションが思ったかんじではないので調整してまた聞く、の繰り返しを土日のあいだ飽きることもなくやっていた。

小学生を卒業したころだったかもしれないけど、ヤマハがミッドラジオプレイヤーというソフトを公開した。

ミッドラジオプレーヤ

ミッドラジオプレイヤーはソフトウェア音源を内蔵していて、スコアメーカーに再生させるよりずっとリッチな音色になるし、なによりそれが無料で使えるのでヤマハ最高! と唱えながら毎日使っていた。

その後に MP3 というものの存在を知ることとなる。MP3 はダウンロードに時間がかかるけどなにやらきれいな音で MIDI が聞ける、という理解のものだった。

後に MP3 は音声を録音するためのフォーマットのことであり、自分が言っていた MP3 はいいソフトウェア音源の演奏を録音したものだと知る。わからなかったことがわかってよかった、と思う一方で、自分が MP3 を作れたとしてもソフトウェア音源を買わなければいけないことを知ってがっかりした。

しかし自分にはミッドラジオプレイヤーがあった。ミッドラジオプレイヤーで再生してそれを録音すればいいということを思い付いたときは自分が天才かと思った。

作った曲はヤマハの音楽投稿サービス (もうなくなった) に投稿していた。

www.itmedia.co.jp

打ち込みしてるうちにいい曲ができたので、中学生にあがってから朝日作曲賞に応募したことがあった。

打ち込みしていたのは悪夢っぽくはないけど、できた曲のことを思い出すと悪夢っぽい。

近況

YAPC::Asia Tokyo 2015

yapcasia.org

採択された。

App-xslatert と Text-Xslate-AST-Walker

github.com

App-xslatert というツールを作り始めた。

App-PRT を便利に使っていて、Perl のコードのリファクタリングはだいぶ楽になったので、テンプレートのリファクタリング、たとえば変数名を変えるといったことを同じような使い心地で行いたいと思ったので作り始めた。

github.com

前に作った Text-Xslate-AST-Walker を使っている。AST を変更する機能が必要になったので issue の優先度を上げたりした。

github.com

mackerel-agent で便利に使える OS X 向けプラグインを Homebrew でインストール

github.com

mackerel-agent-plugins-osx というリポジトリを非公式に作って置いておくという活動を前からしている。今のところをバッテリ残量をホストのメトリクスとして投稿できるのみ。

これを新しい MacBook にインストールする上で手軽にしたいと思って、Homebrew の Tap を作った。

github.com

どうぞご利用ください。

OS X のセットアップを Ansible にだいたい任せる

新しい MacBook Pro がやってきたのでセットアップの記録を書いておく。

「だいたい」とあるように、実際のところ自分の手を動かさざるを得ない手順はまだまだあるし、むしろ増えたりもしている。

手順

  • App Store.app で Xcode をダウンロード
  • System PreferencesSecurity & PrivacyFirewall からファイアウォールを有効にする
  • System PreferencesSharing を開く
    • Computer Name を変更する (今回は izanagi にした)
    • Remote Login を有効にして sshd を起動する
  • Ansible Playbook を現マシンから SSH 越しに適用する
  • System PreferencesKeyboardModifier Keys ... から caps lock を control に (後述するが OS X におけるこの修飾キーの設定の扱いは思ったよりも込み入っていたので手作業することにした)
  • Xcode.app を開いて additional components と以下をインストール:
  • System PreferencesSecurity & PrivacyPrivacyAccessibility から Karabiner を許可する
  • (再度 Ansible Playbook を適用する)
  • Alfred の Powerpack License を入力する
  • Dropbox に認証情報を入力し同期させる

Ansible の Playbook について

github.com

今回、スクラッチから書いてみた。

以前にも Playbook を書いて公開しているのだけれども、Ansible の雰囲気に慣れながら書いていったものなので「今だったこう書くな・こう分けるな」と思っていたことと、Playbook に書いていた設定やインストールすべきソフトウェアにけっこう手垢がついており本当に必要な設定・ソフトウェアだけに絞りたい、という目的があってスクラッチから書いた。

また、前に書いた Playbook では別のホストでも実行できるような汎用性を持たせようとしていたけれども結果的に破綻して実現できていなかった反省とそもそもそのような汎用性は用途上不要だと考えて、今のマシンに特化させることにした。

書く時に考えた果たすべき冪等性の基準として「変更を加えない限り何度実行しても失敗しないこと」を掲げた。

「失敗しないこと」が意味するものは、Ansible の出力が failed にならないこと。

Ansible では task を実行した結果、副作用が及ぼされると changed というステータスになる。

Ansible のコアモジュールでは設定で記述した状態に既に収束していた場合、変更は実際に行わないように書かれている。

また、「変更を加えない限り」というのは、たとえば ssh でログインしてディレクトリが既に作られている場合などを特別に考慮しない、ということ。だいたいは Ansible のコアモジュールがカバーできるし、それらでカバーできない場合はおそらく自分でひとつモジュールをひとつ作るに相当する労力をかけることになるので、それはコストに見合わないと考えた。

また、前提条件をはっきりさせておかないと考慮すべき条件が爆発するという問題もある。

OS X の修飾キーの設定について

apple.stackexchange.com

上記 stackexchange のスレッドに書いてあることがすべてだが:

  • Property list でキーボードの修飾キーを設定する際には識別子が必要
  • MacBook シリーズの本体備え付けのキーボードは内部的には USB 接続のデバイスとして識別されている

……ということらしく、けっこうめんどうそうだったので諦めて手作業することにした。

Path::Tiny を継承する

Path::Tiny - search.cpan.org

Path::Tiny には new という、いかにもオブジェクト指向的なコンストラクタのような名前のメソッドが定義されているのでいかにもオブジェクト指向的に継承できるように見えるが結論から言うと できない

sub new { shift; path(@_) }

Path-Tiny/Tiny.pm at 37c371dbd100d82968b57bafc52222463ceb53bd · dagolden/Path-Tiny · GitHub

This is just like C, but with method call overhead. (Why would you do that?)

とあるので、ドキュメントにある通りとも言える。

どうしても継承したい場合は再度 bless を呼べば上書きされる。

package My::Path;
use parent qw( Path::Tiny );

sub new {
  my ($class, $path) = @_;
  my $self = $class->SUPER::new($path);
  return bless $self, $class;
}

Git で特定のリビジョンから tarball やディレクトリを作る

git-archive を使うと worktree に変更があるかどうか (dirty かどうか) を気にしなくてよくて便利。

moznion.hatenadiary.com

上記のようなことをやってみた例:

gist.github.com

Jenkins の REST API

  • http://JENKINS_HOST/job/JOB_ID/api/json
    • 設定されているパラメータとか見える
    • 最近のビルドとか見える
  • http://JENKINS_HOST/overallLoad/api/json
    • 負荷統計が見える
    • http://JENKINS_HOST/load-statistics で見えるのと同じ?

GitHub の Deployments API を使ったデプロイのワークフローのイメージ

GitHubDeployments API を使うと Web アプリケーションのリリース (デプロイ) に関わるワークフローをより便利にできそうだったので、試したことを記録する。

Deployments API でできること

すべてドキュメントに書いてあるが、かいつまむと:

  • 「デプロイ」を表現するイベントを作ることができる
  • 進捗 (e.g. 成功、実行中, etc.) を表現できる
  • (「デプロイ」を表現するイベントに紐付くメタデータ (e.g. 説明、payload) を作ることができる)

……という具合である。

つまり GitHubAPI は具体的なデプロイのタスクについて責務を負うことはなく、「デプロイ」というイベントをリポジトリにアーカイブしそれらを通知する責務のみを負う、ということになる。Webhook のひとつと言い換えてもよい。

Deployments API と Commit Status API の context について

Deployments を作成する時に required_contexts というパラメータを渡すことができる。

ドキュメントには:

By default, commit statuses for every submitted context must be in a ‘success’ state. The required_contexts parameter allows you to specify a subset of contexts that must be “success”, or to specify contexts that have not yet been submitted. You are not required to use commit statuses to deploy. If you do not require any contexts or create any commit statuses, the deployment will always succeed.

……とある。

つまり、これから作成する Deployments が参照しているコミットの Status について、required_contexts で指定して context に対応する Status が成功した状態であることを求める、ということになる。

context については、Commit Status API において:

Statuses can include a context to indicate what service is providing that status. For example, you may have your continuous integration service push statuses with a context of ci, and a security audit tool push statuses with a context of security. You can then use the combined status endpoint to retrieve the whole status for a commit.

……とある。

たとえば Jenkins でのビルド状態を表す ci/jenkins であるとか、ステージング環境におけるチェックを表す check/staging などが、考えられる。

実際には、これら2つの API を組み合わせて、たとえば次のようなワークフローが考えられるかもしれない:

  • CI でビルドが成功すると Commit Status APIci/jenkins が成功したとラベル付けされる
  • 手動でのステージング環境におけるチェックが済んだら「hubot: staging is ok」と発言すると Hubot が Commit Status APIcheck/staging が成功したとラベル付けされる

実際のイメージ

staging でのチェックが進行中で、大安を待つことは既に成功している、ということが伺える。

staging のチェックも完了した。"Caution to Merge" も消えている。

まとめ