Vagrantをリモートで使いたい (けど妥協した)

VM で開発用のサーバを構築するにあたって、開発用PCではなく他の高スペックPCを母艦にしたいよねーという話。*1


で、とりあえず「vagrant リモート」でググってみれば、やはり同じことを考える人がいたようだ。

VagrantでリモートのVirtualboxを実行するvagrant-remoteを作ってみました - @masuidrive blog
vagrant-remote


公開されているスクリプトを拝見すると、アイディアとしては ssh でつないで vagrant コマンドを叩いているようだ。
なるほどと思いつつも、母艦にしたい PC に vagrant をインストールしたくない、コマンド実行のたびに ssh 接続しているので時間かかりそう、ということで残念ながらこのスクリプトは見送ることにした。


さてどうしたものかと考えてみたところ、自分の要件としては

  • 母艦に ssh 接続は可能
  • VM の設定、OS のインストール、プロビジニングは手動で行う
  • 基本的にひとつの VM を使い続ける(破棄して作り直したりはしない)
  • VM の起動・終了・スナップショットが取れれば充分
  • 対象の VM ソフトは VirtualBox のみ

くらいだったので、「あれ? これって母艦に ssh でつないで直接 VirtualBox のコマンド叩けば良くね?」ということに気が付いてしまった。


ただ、VirtualBox のコマンドを覚えるのはちょっとしんどいので、さくっと vagrant 風の簡単なラッパースクリプトを作ってみた。


murank/vbox.sh


使い方としては、母艦の適当なフォルダにダウンロード*2して (ssh 経由で) 実行するだけ。

Usage: ./vbox list
              up [--gui] <vm>
              down <vm>
              suspend <vm>
              sandbox [list|begin|rollback|commit] <vm>


これで簡単に Infrastructure as Code を試せるようになったので、次はセットアップ用の playbook を書かねば…。

*1:開発用PCでも VM 動かせるけど、それでPCの動作がモッサリするのが嫌だったので

*2:シンタックスハイライト用に拡張子付けてるけど、ファイル名はお好きに

他の Cookbook の Attribute を部分的に読み込ませたい (けど無理っぽい)

環境ごとに Attribute を定義したいのだけれども、Environment はバージョン管理できないっぽい*1し、Attribute を定義する Cookbook を環境の数だけ作るのも DRY ではないので、ひとつの Recipe で各環境用の Attribute File を用意し、他の Cookbook から参照させることはできないかなーと思ったのでいろいろ試してみた。


イメージ的にはこんな感じ。

本番環境用: attributes/production.rb
開発環境用: attributes/develop.rb


結論から言うと、これを実現するのはかなり難しいことが分かった。


まず、他の Receipe の Attribute を参照する方法として、

  1. metadata.rb の depends に追加する
  2. include_attribute を使用する
  3. run_list に追加する

の 3 通りの方法がある。


そして、Chef の仕様として、attributes 配下のすべての Attribute File が読み込まれるとのこと。

An attribute file is located in the attributes/ sub-directory for a cookbook. When a cookbook is run against a node, the attributes contained in all attribute files are evaluated in the context of the node object. Node methods (when present) are used to set attribute values on a node. For example, the apache2 cookbook contains an attribute file called default.rb, which contains the following attributes:

About Attribute Files ― Chef Docs


これらを踏まえて Attribute File の読み込みの有無を確認してみた結果が以下の通り。
(各 Cookbook に attirbutes/default.rb と attributes/default2.rb を用意して確認してみた)

default.rb default2.rb
0. 自 Cookbook の Attribute
1. medadata.rb に追加
2. include_attribute を使用*2
3. run_list に追加

○…読み込まれる ☓…読み込まれない


ご覧のようにほぼすべての場合で、全部の Attribute File が読み込まれた。
唯一、include_attribute を使用した場合だけ、特定の Attribute File を読みこませることができたが、include_attribute は Attribute File 内でしか使用できないため、include_attribute 先を切り替えるための Attribute をまた指定しないといけないという卵と鶏問題が発生する。



というわけで諦めて環境ごとの Cookbook を用意することにしたのであった。

*1:Chef Server での話。ただ、試したわけではないので、もしかしたらできるのかも

*2:include_attribute "other_cookbook::default2"

wxWidgets のマニュアル翻訳始めました

知る人ぞ知る GUI ツールキット wxWidgets を最近使ってみようかと思っていて、その勉強がてら、かれこれ 4 年近く更新の止まっているwxWidgets (wxWindows) 日本語ドキュメントプロジェクト の後を引き継いでみようという無謀な試みお話。


新プロジェクトページ: wxWidgets 日本語ドキュメントプロジェクト



翻訳の方針としては 訳文としての正しさ <<<<< 意味の分かりやすさ でやっているので、誤訳してる可能性大です。
しかも、旧プロジェクトと違って原文を併記していないので、訳が間違っていてもぱっと見分からないというハードモードっぷり。
もしも誤訳・typo を見つけたら GithubIssues ででも報告してください。


また、とりあえず wxWidgets の使い方全般が理解できる (と思われる) 概要から訳していこうと思ってます。
それ以降は適当に使いそうなクラスや関数を順番にやっていこうかと。


翻訳は OmegaT とかいうやつを使ってるので、興味がある方はリポジトリを clone して試してみてください。*1



あとは万が一、途中で挫折しても誰かがフォークして引き継いでくれるはず…。

*1:OmegaT の使い方はまだよく分かってないので、質問されてもさっぱり答えられませんが

git push で自動デプロイ時に git pull は使わない方がいいかもしれない

heroku みたいな git push で自動デプロイする方法として、よく post-recceive フックで git pull する方法が載っているけど、
自分の場合、それがうまくいかなかったことがあったのでメモ。



よくある post-receive フックの内容は以下の通り。

#!/bin/sh

(
cd /path/to/repository
git --git-dir=.git pull origin master
)


普通の使い方をしている限り、この方法で問題なく動作する。
が、うっかり git push -f してしまうといろいろとややこしいことになる。*1


というのも、git pull は結局 git fetch + git merge でしかないので、(本当はなかったことにしたい) 以前の HEAD と新たな HEAD をいい感じにマージしてしまうからこんなことになる。


デプロイ先ではコミットなんてしないんだからマージなんかせずに素直に HEAD を移動するだけでいいじゃない、というわけで代わりに git reset を使うようにしてみたのが以下の通り。

#!/bin/sh

(
cd /path/to/repository
GIT_DIR=.git
git fetch
git reset --hard origin/master
git clean -fdx
)


順に解説していく。

GIT_DIR=.git

複数回 git のコマンドを実行するのに毎回オプションを指定するのも面倒なので、環境変数で指定するようにした。

git fetch
git reset --hard origin/master

git pull が git fetch + git merge なら、こっちは git fetch + git reset だ!というわけでやってみました。

git fetch のオプションに --prune や --no-tags を指定したり、refspec (+refs/heads/master:refs/remote/origin/master みたいなの) を指定したりすると余計なものまで fetch しなくなるので高速化が図れるかも。*2

git reset に --hard を指定しているのはワークツリーごと HEAD を変更しないと意味がないから。
詳しくは 前回記事 参照。

git clean -fdx

git reset --hard をしたときにバージョン管理対象外のファイルが出来てしまうことがあるので、それを削除するために入れている。*3



というわけで、git pull は使い所を間違えると怖いよね、というお話。
そもそもリモートに送ったコミットを書き換えるなって?
自分ひとりで使ってる非公開リポジトリだからいいんですよ…。

*1:取り消したつもりの内容が残ってしまったり、コンフリクトしてにっちもさっちもいかなくなったり

*2:前者はちょっと違うけど

*3:間違ったファイルをコミットして、それを削除してコミットし直すとこういうことが起きる

Gitlab 4.2 + Gitolite で Git フックを使う

Gitlab で作ったプロジェクトでフックを使おうとしたら結構苦労したのでメモ。


以下のディレクトリ構成は Gitlab のインストール手順に従った場合のものなので、Gitolite を手動インストールしたような場合は適宜読み替えを。



まず、Gitolite の機能として、/home/git/.gitolite/hooks/common 配下に入っているファイルをリポジトリ作成時に
自動的に hooks 配下へシンボリックリンクしてくれる機能があるっぽい。

そして、デフォルトでは上記の hooks/common 配下には Gitolite が update フックを、Gitlab が post-receive フックを配置している。


pre-receive フックや post-update フックが必要なのであれば普通に hooks/common へ配置するだけでいいけど、
今回はまさに update フックと post-receive フックを使いたかったので苦労したという話。



単純に考えると、既存の update フックや post-receive フックを自前のフックに置き換えてしまって、
そのフックの中から元のフックを読んでやればいいと思うだろう。

実際、post-receive についてはそれでうまくいった。*1


ところが、update フックについては、リポジトリを作るたびに Gitolite が勝手に元に(Gitolite 用のフックに)戻してしまうようだ。



で、Google 先生でいろいろ調べてみると、 update.secondary という名前でフックを作ればいいらしい*2 とのことなので、
hooks/common/update.secondary を用意してみたものの、さっぱり動かない。



なんでだろー、と公式マニュアルをよくよく見てみると、

WARNING: This is not the latest gitolite; please see the README

という警告が…。



最新のマニュアルを辿って行くと、自前の update フックを使うには、VREF(virtual refs) というものを使うことが分かった。*3

具体的にいうと、update フックとして my-own-hook を呼ばせたい場合、conf/gitolite.conf を

repo @all
    -       VREF/my-own-hook        =   @all

とすればいいらしい。


特定のリポジトリのみに指定したい場合、「repo @all」の「@all」をリポジトリ名に変えれば OK っぽいけど、
リポジトリの設定は Gitlab がガンガン書き換えるので、「repo @all」にしてフック側で $GL_REPO を見て
判断させた方がいいと思う。



というわけで、早速 git clone git@localhost:gitolite-admin して conf/gitolite.conf の変更後、git push する。

そして、自前 update フックだけれども、hooks/common 配下ではなく、VREF ディレクト*4 に置かないといけない。


フックの場所が hooks/common と VREF に別れるのが嫌だけど、そこはシンボリックリンクとかでなんとか誤魔化すということで。



ただ、ここまでやっておきながら Gitlab の v5 では Gitolite 使わなくなるらしいので、
バージョン上げた時はまた苦労するんだろうな…。

*1:Gitlab のプロジェクト一覧ページで post-receive フックが正しくないという警告が出るようになったけど

*2:こことかこことか

*3:ここの 1.3.2.2 update hook 参照

*4:gitolite query-rc GL_BINDIR で表示されるディレクトリ中の VREF ディレクトリのことらしい。自分の場合、/home/git/gitolite/src/VREF だった。

Quick JUnitで快適TestNG生活

何を言っているのか分からねーと思うが(ry


たまたま Quick JUnit を使う機会があって、テスティングペアの切り替えやテストの実行がワンタッチでできることにいたく感動したんだけれども、いかんせん JUnit ではパラメータ化テストが面倒くさい。
その点、TestNGは至れりつくせりということで、「Quick TestNG」がないものかと Google 先生に尋ねてみたものの、簡単には見つからなかった。
なければ作ればいいじゃない、とも思ったけど、なんと TestNG には JUnit からの migration 機能があるとのこと。


これはいけるんではと思って試したところ、うまくいったのでメモ。
試した Eclipse は 3.7 Indigo。

事前設定

まずは Quick JUnitTestNG のインストール。
インストール方法の詳細は各HPで。


続いて Quick JUnit のショートカットキーを TestNG のものに差し替える。
[ウィンドウ]→[一般]→[キー]を開いて以下の変更を行う。

コマンド 変更前 変更後
JUnit テスト Ctrl+0 アンバインド
Junit デバッグ Ctrl+Shift+0 アンバインド
Run TestNG Test Alt+Shift+X, N Ctrl+0
Debug TestNG Test Alt+Shift+X, G Ctrl+Shift+0

変更後のショートカットキーはお好みで&他のショートカットキーも必要に応じて変更するといいかも。

プロジェクト作成時の操作

TestNG をプロジェクトの依存ライブラリーに追加する。
プロジェクトの[プロパティ」→[Javaのビルド・パス]→[ライブラリー]で[ライブラリーの追加]をクリックし、「TestNG」を追加する。

テストクラス作成時の操作

テスト対象のクラスで Ctrl+9 すると自動的にJUnitテストクラスが作成される。
この時、JUnit をビルドパスに追加するか聞かれるので追加する*1


作成されたテストクラス内でクイックフィックスを開き(Ctrl+1)、「Convert to TestNG」を選択する。
(パッケージエクスプローラ上から右クリック→ [TestNG]→[Convert to TestNG]でも可)


あとはTestNG用のテストクラスに変換されるので Ctrl+9 で TestNG が実行できる。



これでTDDも捗るというものよ。

*1:追加しなくても問題ないけど、毎回聞かれるので追加しておく

CloseHandle 系関数に INVALID_HANDLE_VALUE (またはNULL)を渡してはいけない

free や delete に NULL を渡しても問題ないというのは有名な話*1だけど、Win32 API はどうなんだろうという話。


できれば公式情報として「問題ナイヨー」という保証が欲しいところだけど、いまいち MSDN を見てもよく分からない。
INVALID_HANDLE_VALUE に少しでも関係していそうな事といえば、「関数が ERROR_INVALID_HANDLE を返したら(関数が失敗したら) 大抵の場合はCloseHandle しなくてもいいよ」程度しかない。

It is usually not necessary to call CloseHandle if a function that uses a handle fails with ERROR_INVALID_HANDLE, because this error usually indicates that the handle is already invalidated.

http://msdn.microsoft.com/ja-jp/library/ms724211


仕方がないので実際に試してみた結果が以下の通り。*2

関数 戻り値 GetLastError エラーメッセージ
CloseHandle (NULL) 0 (失敗) 0x00000006 (ERROR_INVALID_HANDLE) ハンドルが無効です。
CloseHandle (INVALID_HANDLE_VALUE) 0 (失敗) 0x00000006 (ERROR_INVALID_HANDLE) ハンドルが無効です。
FindClose (NULL) 0 (失敗) 0x000003E6 (ERROR_NOACCESS) メモリ ロケーションへのアクセスが無効です。
FindClose (INVALID_HANDLE_VALUE) 0 (失敗) 0x00000006 (ERROR_INVALID_HANDLE) ハンドルが無効です。
RegCloseKey (NULL) 0x00000006 (ERROR_INVALID_HANDLE) - ハンドルが無効です。
RegCloseKey (INVALID_HANDLE_VALUE) 0x00000006 (ERROR_INVALID_HANDLE) - ハンドルが無効です。
CloseThemeData (NULL) 0x80070006 (E_HANDLE) - ハンドルが無効です。
CloseThemeData (INVALID_HANDLE_VALUE) 0x80070006 (E_HANDLE) - ハンドルが無効です。
UnmapViewOfFile (NULL) 0 (失敗) 0x000001E7 (ERROR_INVALID_ADDRESS) 無効なアドレスにアクセスしようとしています。
UnmapViewOfFile (INVALID_HANDLE_VALUE) 0 (失敗) 0x000001E7 (ERROR_INVALID_ADDRESS) 無効なアドレスにアクセスしようとしています。
FreeLibrary (NULL) 0 (失敗) 0x0000007E (ERROR_MOD_NOT_FOUND) 指定されたモジュールが見つかりません。
FreeLibrary (INVALID_HANDLE_VALUE) 0 (失敗) 0x000000C1 (ERROR_BAD_EXE_FORMAT) %1 は有効な Win32 アプリケーションではありません。
DeleteDC (NULL) 0 (失敗) 0x00000006 (ERROR_INVALID_HANDLE) ハンドルが無効です。
DeleteDC (INVALID_HANDLE_VALUE) 0 (失敗) 0x00000006 (ERROR_INVALID_HANDLE) ハンドルが無効です。
DeleteObject (NULL) 0 *3 0 -
DeleteObject (INVALID_HANDLE_VALUE) 0 *4 0 -
ReleaseDC (HWND, NULL) 0 *5 0 -
ReleaseDC (INVALID_HANDLE_VALUE) 0 *6 0x00000591 (ERROR_DC_NOT_FOUND) バイス コンテキスト (DC) ハンドルが無効です。


ご覧のように NULL や INVALID_HANDLE_VALUE を渡すと見事にエラーになった。
もっとも、この検証中にプログラムがクラッシュすることはなかったので、エラーを無視するというのもひとつの手かもしれない。
が、お行儀のいいプログラムを書くためにも、できればハンドルをクローズする前に INVALID_HANDLE_VALUE(NULL) のチェックを行うべきとは思う。


そしてこの結果から、以下のようなコードはよろしくないということが分かる。

// これはダメ
std::tr1::shared_ptr<std::tr1::remove_pointer<HANDLE>::type> ptr(CreateFile(...), CloseHandle);


void SafeCloseHandle(HANDLE h)
{
	if(h != INVALID_HANDLE_VALUE) {
		CloseHandle(h);
	}
}

// これならOK
std::tr1::shared_ptr<std::tr1::remove_pointer<HANDLE>::type> ptr(CreateFile(...), SafeCloseHandle);

いちいちラッパー関数を噛ませないといけないのはいまいちだなぁ。

*1:[迷信] free に NULL を渡すとクラッシュする | 株式会社きじねこ

*2:検証環境は WinXP SP3、Visual Studio 2008 SP

*3:不正なハンドルを渡したので 0 が返る

*4:不正なハンドルを渡したので 0 が返る

*5:バイスコンテキストを解放できなかったので 0 が返る

*6:バイスコンテキストを解放できなかったので 0 が返る