Chef Solo で invalid byte sequence in Windows-31J / invalid byte sequence in UTF8 に遭遇したときの対処をいくつか

Chef-solo に同梱されている Ruby は 1.9.3p286 なので、エンコーディングが 1.8.x に比べて改善されているらしい。が、やはり日本語 Windows を使ってるといろいろハマるのでメモ。

前提

環境変数 LANG がセットされていない場合、

ECHO %LANG%
=> (空)

外部エンコーディングは Windows-31J

ruby -e "p Encoding.default_external"
=> #<Encoding:Windows-31J>

最近の風潮としてエンコーディングの基本はUTF8なので、環境変数 LANG をセットしておく。

SET LANG=ja_JP.utf8

そうすると外部エンコーディングも UTF8 になる。

ruby -e "p Encoding.default_external"
=> #<Encoding:UTF-8>

で、ソースコードはマジックコメントでUTF8を指定しておく。

# -*- encoding: utf-8 -*-

基本はこれで問題ないんだけども、 日本語 Windows では Windows-31J と付き合わないわけにはいかないのでまれに問題がおきる。

windows_batch リソース

何らかのコマンドに対して日本語で引数を指定する場合次のように書くわけだが、受け側ではShiftJISを期待してる。
ところがマジックコメントでUTF8を指定しているため、受け側には文字化けした文字列がわたってしまう。

windows_batch "sample" do
    code = <<-EOH
    hoge 日本語
    EOH
end

なのでコマンドに日本語を渡したい場合は Windows-31Jエンコードしましょう。

windows_batch "sample" do
    tmp = <<-EOH
    hoge 日本語
    EOH
    code tmp.encode('Windows-31J', 'utf-8')
end

windows_package リソース

「アプリケーションの追加と削除」に登録されるようなアプリケーションをインストールしてくれる便利なコマンドなんだけど、やっぱり日本語が入るとエラーが発生します。
難点なのが、目当てのアプリケーションに日本語が入ってなくても、アプリケーションがインストールされているかのチェックでレジストリを参照すること。このときに日本語を含むアプリケーションがあるとエラーが発生してしまう。

windows_package "Fuga Application" do
    source "path/to/fuga"
    installer_type :customoptions "/q"
    action :install
end

これはレシピをいじっても仕方なくて、同梱されている Ruby ライブラリの問題。具体的には win32/registry.rb 。これを以下のように修正してあげればいいい。

+++ C:/opscode/chef/embedded/lib/ruby/1.9.1/win32/registry.rb	Thu May 09 11:12:35 2013
@@ -165,11 +165,14 @@
         dlload "kernel32.dll"
       end
       FormatMessageA = Kernel32.extern "int FormatMessageA(int, void *, int, int, void *, int, void *)", :stdcall
+      FormatMessageW = Kernel32.extern "int FormatMessageW(int, void *, int, int, void *, int, void *)", :stdcall
       def initialize(code)
         @code = code
         msg = "\0".force_encoding(Encoding::ASCII_8BIT) * 1024
-        len = FormatMessageA.call(0x1200, 0, code, 0, msg, 1024, 0)
-        msg = msg[0, len].force_encoding(Encoding.find(Encoding.locale_charmap))
+        #len = FormatMessageA.call(0x1200, 0, code, 0, msg, 1024, 0)
+        len = FormatMessageW.call(0x1200, 0, code, 0, msg, 1024, 0) * 2
+        #msg = msg[0, len].force_encoding(Encoding.find(Encoding.locale_charmap))
+        msg = msg[0, len].force_encoding("UTF-16LE").encode(Encoding.find(Encoding.locale_charmap))
         super msg.tr("\r", '').chomp
       end
       attr_reader :code

Chef-soloでプロキシ設定の切り替え

今までサーバー設定なんかは Fabric でいろいろやってきたわけなんですが、 Trac をホストしてた Windows サーバーのリプレースをする必要がでてきたので、最近注目してた Chef でいろいろやってみました。

が、 Ruby 初心者にはいろいろ難しく、なんといってもあまり Windows 向けではないので、結構よく詰まります。なので備忘録を兼ねてちょっとずつメモしていく予定。


Chef-solo の場合、 solo.rb という設定ファイルにツール設定をしていくんだけども、大抵はファイルキャッシュとクックブックの場所を設定するだけでいい。

# solo.rb
# -*- encoding: utf-8 -*-
file_cache_path File.join(Dir.pwd, 'cache')
cookbook_path   File.join(Dir.pwd, 'cookbooks')

普段はこれで問題ないんだけども、職場などプロキシ環境下にいるときは設定を追加しないといけない。

# solo.rb
# -*- encoding: utf-8 -*-
file_cache_path   File.join(Dir.pwd, 'cache')
cookbook_path     File.join(Dir.pwd, 'cookbooks')
http_proxy        "http://path/to/proxy:8080"
https_proxy proxy "http://path/to/proxy:8080"

別のマシンをセットアップするならともかく、1台のマシンをセットアップするのに別の設定ファイルを用意するのはなんか違う、ということで自動的にプロキシの設定ありなしを判断するようにしてみたのがこれ。

# -*- encoding: utf-8 -*-
file_cache_path File.join(Dir.pwd, 'cache')
cookbook_path   File.join(Dir.pwd, 'cookbooks')


require('win32/registry')
key = 'Software\Microsoft\Windows\CurrentVersion\Internet Settings'
reg = Win32::Registry::HKEY_CURRENT_USER.open(key)
if reg["ProxyEnable"] == 1
    proxy = "http://" + reg["ProxyServer"]
    http_proxy  proxy
    https_proxy proxy

    ENV['http_proxy'] = proxy
end

単純にIEなどで使われるインターネット接続設定をレジストリから取得してるだけ。環境変数ENVに[http_proxy]をセットしてるのは、ほかのツールなどで使われることを想定するため。これでどこでも開発が進められますね。