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" も消えている。

まとめ

Handlebars テンプレートをコンパイルした結果をファイルに出力する gulp タスク

aereal/gulp-handlebars-playground · GitHub

HTML を出力するためのタスクを用意してみた。

  • gulp
    • gulp-data
    • gulp-compile-handlebars
    • gulp-rename

……を使った。

gulp-data

stream に data を追加できる。

gulp-compile-handlebars などのようにパラメータを受け取る gulp のプラグインがサポートしることが条件。

パラメータに渡すオブジェクトを JSON ファイルや DB から引いて作りたい、という時に同期的に見えるような書き方ができるように隠蔽してくれる。

Ansible の Variables の名前付けとデフォルト値の上書き

role で使う変数の名前が衝突しないよう配慮すると、素朴に辞書を定義して名前空間を導入したいと考えると思う:

--- # roles/mackerel-agent/defaults/main.yml
mackerel_agent:
  pid_file: '...'
  id: '...'

ところが Ansible の Variables は辞書の deep merge を行ってくれないので、デフォルト値の一部上書きができない:

--- # host_vars/app001.yml
mackerel_agent:
  id: '...'
  # !!! mackerel_agent.pid_file が未定義になる !!!

なので辞書を用いて衝突を回避するのは諦めて、気をつけるしかなさそう:

--- # roles/mackerel-agent/defaults/main.yml
mackerel_agent_pid_file: '...'
mackerel_agent_id: '...'

テストの妥当性

ソフトウェア開発におけるテストは仕様の表現のひとつという風にも扱われるなど、重要な役割を担う一方で、テスト自体の妥当性の検証や保証は少なくて、こういう不安定な土台の上でいろいろやっていいのか、という気がする。

レビューする時には、まずテストコードを眺めて、大抵が自然文で表現された仕様と照らし合わせながら、矛盾がないかとか足りないテストケースがないか、とか考える。

ソフトウェアテストについてちゃんと勉強していけば、どういうテストケースが必要か *1 とかどういう風に表現すればよいか *2 はわかってくると思う。けど、人間の知識や理解に委ねられている、というのはどうにも不安に思える。

テストに対するテスト、メタテストみたいなのがあって、こういうテストケースが足りていない、とかわかるとよいと思う。

今思ったけど、カバレッジとかがそれにあたるのではないか、と思った。

今のチームでは一日に一回、カバレッジをとっていて、たまに眺める、という風になっている。本当はトピックブランチなどでテストの追加・削除を行ったらその都度カバレッジの変化が見れるとよいのだろうと思う。

*1:正常系、異常系、境界条件など

*2:stub, mock, など

Module が要求する実装を宣言して、実際に要求を満たしているか検証する

タイトルにあることを Ruby でやる実装を書いた:


aereal/module-requirement · GitHub

Swift / Objective-C の protocol は具象クラスがメソッドを実装していることを要求できるので、Ruby でも似たようなことをやってみる、というコンセプト。

使い方

require 'module/requirement'

module Iterable
  include Module::Requirement
  include Enumerable

  requires :each
end

class List
  include Iterable # => Module::Requirement::Error::NotMeetRequirements List must implement required methods: each
end

Iterable という each メソッドが定義されていることを期待するモジュールを定義して、List というクラスは Iterableinclude しようとするけど要求されている each メソッドの定義を持っていないので例外が発生する、という様子。

うれしいところ

Ruby はコアに EnumerableComperable といった「要求されるメソッドさえ定義しておけば、後はいい感じに便利なメソッドを使える、というモジュールが用意されている。

そのようなモジュールを自分で定義して提供するときに、要求する実装 (= メソッド) を宣言的に書けて、あまつさえ (実行時でも) 検証してくれるので便利。

いけていないところ

Module::Requirementinclude したモジュールを include する時点で構造を検証するので、次のように書いてもエラーになる:

class List
  include Iterable

  def each
    # ...
  end
end

静的検査に近づこうというコンセプトなので、仕方がないと思いつつ、どうにかなるとよいと思う。

include すると要求するメソッドのボディを raise NotImplementedError と定義する、というのはありかもしれない。

AquaSKK 4.2.1 をインストールする Homebrew Cask を書いた

以前に書いた AquaSKK をインストールする Cask を書いた - Sexual Knowing の続き。

Homebrew Cask で AquaSKK をインストールするには

brew tap aereal/homebrew-aereal_casks
brew install aquaskk

Cask DSL 1.0 をサポート

Homebrew Cask 0.40.0 で DSL の語彙が変更された。0.40.0 以降で brew cask install aquaskk が失敗するようになっていたので修正した。

AquaSKK 4.2.1 をインストールするように

OS X Yosemite で動作するよう修正が加えられている fork より AquaSKK 4.2.1 が配布されているのでこちらをインストールするようにした。

どうぞご利用ください

テンプレートエンジン Night (#tenight) で「テンプレートの静的解析とリファクタリング」について発表した

テンプレートエンジンNight というイベントで Text::Xslate を使ってテンプレートの静的解析とリファクタリングを行った知見について発表した。

テンプレートエンジンを使ってこういうことができる、というような内容から、テンプレートエンジン作った・作ってますというような内容まで、トークの内容に幅があっておもしろかった。

トークで話さなかったこと

使っている変数の宣言コメントはどうやって抽出するのか

正規表現です。

変数として定義することも考えられるし、実際にそうすることも考えてみた。

[% SET INHERITED_VARS = ['post'] %]

しかし既にあるコメントによる宣言を書き換える手間や、現状を把握したいという目的からは逸れてしまうことから見送った。

テンプレートのモジュール化 (分割) の指針

早すぎた抽象化という問題がある (あった) ということは認識しているし、今が最高の状態だと思っているわけではない。

既存のテンプレートのコードを書き換える前に現状を把握するために静的解析を行おう、というモチベーションがあってトークの内容に繋がる、というかんじです。

Mackerel で QNAP も監視したいのでする

最近、MacBookSSD の空き容量がいつの間にか無くなっていて、スワップを作れなくなった結果、フリーズするということがあって反省したのでちゃんと管理しようという気持ちになった。

ひとまず家にある中で壊れたりトラブルが起きると一番困るのは QNAP なので、これを監視することにした。

Mackerel は Go で書かれた agent を入れるだけでよい割にアラートの閾値をいい感じに定義することができるので便利。

QNAP で mackerel-agent を動かす

僕の持っている QNAP は TS-220 というモデルで CPU は ARM v5 で OS は Linux ベースらしい。

mackerel-agent は最近の CentOSDebian しか動作することを保証されていない:

For now, mackerel-agent is guaranteed to run only on CentOS 5/6 and Debian 6/7.

https://github.com/mackerelio/mackerel-agent#readme

とはいえ Go で書かれているし Linux ベースなら取り付く島が無いなんてことはないだろうと思ってちょっと試したら動いた。

テストは書いていないしかなりいい加減だがこれで動いた。

  • QTS (QNAP の OS) に入っている uname-o (Operating System) をサポートしていない
    • どのような意図でもって実装されていないのかは不明だけれどもさして重要な情報ではないので決め打ちで "Linux" とする
  • QTS に入っている df-P オプションをサポートしていないし出力の形式が少し違う
    • 1024-block1k-block だった
    • それ以外は特に書式に大きな違いはなかったので正規表現を修正する

Docker でビルドする

mackerel-agent は OS X ではビルドに失敗するので Docker を使うことにした。

Dockerfile:

FROM golang:cross

RUN mkdir -p /go/src/github.com/mackerelio
ADD ./mackerel-agent /go/src/github.com/mackerelio/mackerel-agent
WORKDIR /go/src/github.com/mackerelio/mackerel-agent
RUN make deps
CMD bash -c "GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=0 make build && cp ./build/mackerel-agent /host/_mackerel-agent"

これで docker build -t mackerel-agent . && docker run -v "$(pwd)":/host mackerel-agent とすると _mackerel-agent が作られる。

Docker Hub Registry には golang が登録されているが、そのうちクロスコンパイルするための環境がセットアップされているタグを選ぶ。最新 (1.3) でよければ cross.

ビルドの際に指定している環境変数Optional environment variables を参照した。 また GOARM 変数については GoArm - go-wiki が詳しい。

ルート証明書が見つからないエラー

ビルドはできたものの実行時エラーが出る:

x509: failed to load system roots and no roots provided

意味がわからなかったが調べたところ x509 は SSL 通信のために使われているらしい。“roots” がよくわからなかったがルート証明書のことらしい。

ルート証明書が無いなら配置すればよいかと思ったもののどこに配置すればよいのかわからない。

Go において CertPool は参照すべき証明書を保持するオブジェクトの型で、SSL 通信などを扱うライブラリ (e.g. net/http) ではデフォルトではシステムの証明書を参照するようだった。

ではシステムの証明書の配置場所はどこかというと root_unix.go というファイルに定義されている。

var certFiles = []string{
        "/etc/ssl/certs/ca-certificates.crt",     // Debian/Ubuntu/Gentoo etc.
        "/etc/pki/tls/certs/ca-bundle.crt",       // Fedora/RHEL
        "/etc/ssl/ca-bundle.pem",                 // OpenSUSE
        "/etc/ssl/cert.pem",                      // OpenBSD
        "/usr/local/share/certs/ca-root-nss.crt", // FreeBSD/DragonFly
    }

QNAP では /etc/ssl/certs/myroots.crt に配置されていたので symlink を作った。

おわり

これで QNAP 上で mackerel-agent を動かすことができるようになったはず。

あとは init スクリプトを書いたりしてデーモン化しておけばよい。

ディスク容量だけではなく netin/netout や CPU 使用状況も見れるので並列にコピーを走らせて無茶を走らせているときなど、余裕があるかなど見れて便利。

だいたい普通の Linux という感じでかなり手軽に運用できるので、次は nasne あたりも監視したい。

Shipped Text-Xslate-AST-Walker @ 0.01

これはなに?

Text::Xslate::Parser が返す AST を文字通り「歩きまわる」ためのモジュールです。

0.01 現在では条件に一致するノードのみを返すメソッド (#search_descendants) が実装されています。

使い方

SYNOPSIS より:

use Text::Xslate::Parser;
use Text::Xslate::AST::Walker;

my $template = <<EOF;
: my $first_name = 'Hanae';
Hello, <: $last_name :>, <: $first_name :>.
EOF
my $parser = Text::Xslate::Parser->new;
my $nodes = $parser->parse($template);
my $tw = Text::Xslate::AST::Walker->new(nodes => $nodes);
my $undeclared_vars = $tw->search_descendants(sub {
  my ($node) = @_;
  ($node->arity eq 'variable') && !$node->is_defined && !$node->is_reserved;
});

printf "Undeclared var: %s @ Line %d\n", $_->id, $_->line for @$undeclared_vars;

簡単ですね。

#search_descendants に渡すサブルーチンには Text::Xslate::Symbolインスタンスが渡されます。

上記例のように未定義の変数を列挙することなどができます。

Text::Xslate は便利

Text::Xslate は高速なテンプレートエンジンとして既に有名で実績がありますが、一方で構文を選ぶことのできる柔軟性も持ち合わせています。

構文それぞれについて Parser が用意されており、抽象構文木を得ることができます。

今後

などを目指しています。

機能追加、バグ報告

GitHub で開発しているので Issues or Pull Request をお待ちしております。

どうぞご利用ください

cpanm Text::Xslate::AST::Walker