ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

packer-provisioner-serverspec というPackerプラグインを書いた

以前、Qiitaでこういう記事を書いたんですが:

qiita.com

これと同じコンセプトで、最低限の設定で作成後のイメージにServerspecを走らせるためのプラグインを書きました。

github.com

インストールは、今のところ自分でビルドする必要があります(もうちょいちゃんとしたらバイナリを配りたい)。

# Needs Go >= 1.3
$ git clone https://github.com/udzura/packer-provisioner-serverspec.git
$ cd packer-provisioner-serverspec
$ make install

設定はこんな感じで。

{
    "builders" : [
        {
            "type": "amazon-ebs",
            "ssh_username": "centos",
            "ssh_pty": true,
            "...": "..."
        }
    ],

    "provisioners": [
        {
            "type": "shell",
            "script": "/path/to/your-provisoner.sh"
        },
        {
            "type": "serverspec",
            "source_path": "/path/to/your/serverspec-root"
        }
    ]
}

今はα版ということ(にしておく)で、いろいろな条件が固定です。

  • sshのユーザはsudoできないといけない
  • "ssh_pty": true でないといけない(Packer 0.8からだそうです。参考
  • rakeタスクは rake spec でないといけない
  • ディレクトリ構成はこんな感じがよさそう
/path/to/your/serverspec-root
├── Gemfile # Gemfileとlock、必ずしも必要なし
├── Gemfile.lock
├── Rakefile
└── spec
    ├── localhost
    │   ├── cloud_init_spec.rb
    │   ├── hosts_spec.rb
    │   ├── packages_spec.rb
    │   └── users_spec.rb # and so on...
    └── spec_helper.rb

Serverspec自体のインストールは omnibus を使っています。 @sawanoboly さんは神です。おかげで、CentOSUbuntuであればひとまず動きそうです。あ、Ubuntuはちゃんと検証していません......

rake spec が終わったらserverspecパッケージとアップしたserverspecのディレクトリは綺麗に消します。

そんな感じで、機能はまだ全然足りないですが、お試しください。


PR

こういうPackerの深い話、具体的にはprovisionerだけでなくbuilderまで自作した事例を、 HackerTackleというイベントで話します。ついでに、 Consulの話とかなんか抽象的な話もしちゃいます。

hakat.connpass.com

なんと! 明日 なんですが、会場かなり余裕があるので、今から登録しても参加できると思います!

九州に住んでいない人も、今から飛行機を取れば全然間に合います!

という感じで、深い話ができ、なおかつ最前線(自分を棚にあげる)のエンジニアに質問できたり、がっつり議論できる貴重な場だと思います。博多でお待ちしております!

Faradayの話 - OpenStack クライアント開発日記 (3)

今日はFaradayの話をする。

github.com

yao の開発の基本方針は、「必要のない依存をしない」だけれど、Faradayは数少ない依存gemに含まれている。

github.com

理由は、

  • Faraday自体の依存gemが少ない
  • 利用することでの、コードをスッキリさせるメリットが非常に大きい

と考えたから。

具体的には、FaradayはRackよろしくMiddlewareで機能追加をするパターンを採用しているため、ちょっとした仕事をさせるためのコードが、短く、わかりやすいものにできる。以下は具体例。

ログイントークンの保持と再発行

OpenStackのAPI利用の流れは、

  • username、パスワード、テナント名(AWSでいうVPCひとつひとつみたいな感じ)を指定し、ログイン用エンドポイントを叩く
  • そのエンドポイントから、ログイン用のトークンと、各サービス(compute、networkなど)それぞれのエンドポイントURL一覧をもらえる
  • トークンとエンドポイント情報を利用してその後のAPIを叩く

という感じになっている。 OpenStack API Quick Start の通りである。

トークンは、 "ac4e1c05084ba4365c1c38bfb1350000" みたいなMD5ハッシュ風の値で、これをヘッダに X-Auth-Token: "ac4e1c05084ba4365c1c38bfb1350000" と含めばOK。なお、この認証方式、微妙にOAuth2と違うので既存のMiddlewareを使いまわせない...

今回は以下のようなMiddlewareを作った。

class Faraday::Request::OSToken
  def initialize(app, token)
    @app = app
    @token = token
  end

  def call(env)
    if @token.expired?
      @token.reflesh(Yao.default_client.default)
    end

    env[:request_headers]['X-Auth-Token'] = @token.to_s
    @app.call(env)
  end
end
Faraday::Request.register_middleware os_token: -> { Faraday::Request::OSToken }

ご覧の通り、これはなんというRack Middleware...という感じである。

ポイントは、 @token 自体は普通にTokenオブジェクトなので、expireしたかどうかの情報も所持している。なので、「Tokenがexpireしていたら、ハンドラーの中で勝手に再発行する」といった実装が可能になっているところ。

レスポンス JSON をそのままダンプする

OpenStackのドキュメントなどに想定されるレスポンスはあるんだけど、実際のものを見た方が理解が早い場合が多い。

こういうレスポンスを保存するやつとしては vcr/vcr · GitHub が有名だけど、リクエストも保存したりなど重量級なのでこんぐらいのコードで対応する。

class Faraday::Response::OSResponseRecorder < Faraday::Response::Middleware
  def on_complete(env)
    require 'pathname'
    root = Pathname.new(File.expand_path('../../../tmp', __FILE__))
    path = [env.method.to_s.upcase, env.url.path.gsub('/', '-')].join("-") + ".json"

    puts root.join(path)
    File.open(root.join(path), 'w') do |f|
      f.write env.body
    end
  end
end
Faraday::Response.register_middleware os_response_recorder: -> { Faraday::Response::OSResponseRecorder }

レスポンスのMiddlewareの場合、Rackっぽくなくて、 on_complete(env) が順次呼ばれ続けるというイメージ。その中でenvに副作用を起こせばいい。

このMiddlewareを有効にすると、通常通りAPIアクセスをしつつ、 PROJECT_ROOT/tmp 以下にそのままレスポンスのJSONが残る。

$ jq . < tmp/POST--v2.0-tokens.json                                                                                    
{
  "access": {
    "token": {
      "issued_at": "2015-08-31T03:58:36.073232",
      "expires": "2015-09-01T03:58:36Z",
      "id": "31a5166533fd49f3b11b1cdce2000000",
      "tenant": {
        "description": "development environment",
        "enabled": true,
        "id": "b598bf98671c47e1b955f8c9660e0000",
        "name": "dev"
      }
    },
    "serviceCatalog": [
      {
        "endpoints": [
....

エラー処理

OpenStack APIのエラーの扱いは、原則として 200..299 の範囲外のものであれば、ステータスコードに対応したエラーになる(300系はない。ないよね?)。

なので、アダプター側で共通化できる。

class Faraday::Response::OSErrorDetector < Faraday::Response::Middleware
  # TODO: Better handling, respecting official doc
  def on_complete(env)
    # Faraday::Env の組み込みメソッド
    # 本来、APIごとに正常なステータスコードの指定があるのだが、後でやる
    return if env.success?

    raise Yao::ServerError.detect(env)
  end
end
Faraday::Response.register_middleware os_error_detector: -> { Faraday::Response::OSErrorDetector }
module Yao
  class ServerError < ::StandardError
    def self.detect(env)
      case env.status
      when 400
        if env.body && env.body["computeFault"]
          ComputeFault.new(extract_message(env.body), env)
        elsif env.body && env.body.to_a.join.include?('NetworkNotFound')
          NetworkNotFound.new(extract_message(env.body), env)
        else
          BadRequest.new(extract_message(env.body), env)
        end
      when 401
        Unauthorized.new(extract_message(env.body), env)
      when 404
        if env.body && env.body["itemNotFound"]
          ItemNotFound.new(extract_message(env.body), env)
        else
          NotFound.new("The resource could not be found.", env)
        end
      when 405
        BadMethod.new(extract_message(env.body), env)
      # ... こんな感じで判定を頑張る
      end
    end
  end
end

エラー種類の判定は、まあ頑張ってメタプログラミングしなくていいかと思ってこうしてる。この辺多分最近Go言語を書いてることが強く影響してそう。。

エラーについては、より注意深くハンドリングしたい場合もあるだろうが、その場合はRequest pathとか(というかenv自体)そういう情報も入れてるので、rescueしてそういうのを見るという設計方針で良さそう。

こんな感じでMiddlewareは簡単に書けて助かる。という話でした。

お知らせ

ノリで 0.0.2 をリリースしました。

yao | RubyGems.org | your community gem host

ConoHaのAPI でも動くんじゃないかという気がする。試してないけど。

OpenStack クライアント開発日記 (2)

重要なお知らせ

プロジェクトの名前が yao になった。

github.com

背景として、OpenStackのPythonのライブラリにすでに Oslo なるものが存在した。

さすがにOpenStackというドメインでかぶってるのはかなり渋いので変更した。(oslo.gemは「oslo gem」とか「oslo ruby」とかで事前検索した結果見つからなかったのでつけてしまった...)

yao というのは Yet Another Openstack libraryの略。工夫がないですね... しかし短いし、何より取れてしまったのでこれで。そこはかとなくアジアンな響き。

sed万歳!

JSONをオブジェクトっぽく扱う実装のメモ

難しいことはしたくなかった。以下の方針でやっていった。

  • イニシャライザに渡すHashを普通に持っておく
  • Resource#[],#[]= をそのHashに移譲する
  • よく使う奴はクラスマクロでまとめてメソッドにする

こういうことやってくれるgem(たとえばhashie)はあるけど、機能が多すぎるし、依存を小さくしたいので自分でひょろっと書く。

OpenStackのJSON、幾つか渋い要素があって、

  • キャメルケースとスネークケースが混ざってる
  • "OS-FLV-DISABLED:disabled" みたいなキーがある

その二点の要件を満たすべくこんな感じで書けるようにした。

module Yao::Resources
  class Flavor < Base
    # そのまんまメソッド名に対応できるキーを列挙
    friendly_attributes :name, :vcpus, :disk, :swap

    # キャメルケースのキー、複雑で渋いキーはエイリアス
    map_attribute_to_attribute "os-flavor-access:is_public" => :public?
    map_attribute_to_attribute "OS-FLV-DISABLED:disabled"   => :disabled?
  end
end

あと、これらのパターンにあてはまらないような属性もあるんで、そういうのはこう、子クラスでメソッドを普通に定義すればいいと思う。普通って素晴らしい。

module Yao::Resources
  class Image < Base
    def size(unit=nil)
      size = self["OS-EXT-IMG-SIZE:size"]
      case unit
      when 'K'
        size / 1024.0
      when 'M'
        size / 1024.0 / 1024.0
      when 'G'
        size / 1024.0 / 1024.0 / 1024.0
      else
        size
      end
    end
  end
end

OpenStack、拡張が幾つかあって環境によって取れる値に微妙に差があるので、全部をメソッド化するのは辛い。その辺は Resource#[] で取ればいいかなと思った。必要ならPRする方向でひとつ。

それからもう一つあって、SecurityGroup関連のリソースが、novaで取得した時とneutronで取得した時とで微妙にフォーマットに差がある...

辛いけど頑張って抽象化した。うまくいってるかは徐々に検証して修正する。ご興味があればこの辺眺めてください。

ここまでで、一応、インスタンスも立てられるぞ!

Yao::Server.create(
  availability_zone: "test",
  imageRef: "b4bd717c-7bf9-4c53-a40b-XXXXXX",
  flavorRef: "123",
  networks: [
    {uuid: "e3783d02-a10e-44d9-8957-XXXXXX"},
    {uuid: "c114be43-5a59-4046-b9c1-XXXXXX"}
  ],
  name: 'udzuradayo.novalocal',
  key_name: 'sample001'
)

そしてそろそろ

自動テストを書かないとね...

OpenStack クライアント開発日記 (1)

github.com

いろいろな事情から作っている。

  • fog というものがあって、社のOpenStack系ツールでgemのものはこれを使っているが、ものすごい数の依存gemがあり、fog-openstackへの切り出しの動きは非常に鈍く思えるので、なんというか、いかつい。
  • openstack-ruby は最終更新が2013年で、これを頑張って使おうという気にはなれない...

OpenStack API Documentation などを眺めれば分かる通り、OpenStackのAPIは基本的には非常に綺麗なRESTにしたがっている。なので、自分でスクラッチで書いてもそんなに手間じゃないのでは??と思って書いてみているという話。

方針は:

  • 依存をなるべく少なく
  • メタプロもなるべく...少なく?

進捗

規約に従ってREST APIをレスティに叩くためのメソッドを定義(list, find, create, update, delete, おまけで list_detail)し、サブクラスでパラメータ的な箇所をこうやって指定。

module Oslo::Resources
  class Server < RestfulResources
    self.service        = "compute"
    self.resource_name  = "server"
    self.resources_name = "servers"
  end
end

依存を少なくするんだ!!! と思っているので、 ActiveSupport::Inflector の機能は使いたくない... でも同じようなものを自分で書くことになるんかな...

これでインスタンス一覧が取得OK。詳細もOK。

Oslo::Resources::Server.list
=> [{"id"=>"546143ff-7b7c-4423-9bcf-ce193b74e5ef",
  "links"=>
   [{"href"=>"http://nova.exapmle.com:8080/v2/b598bf98671c47e1b955f8c9660e3c44/servers/546143ff-7b7c-4423-9bcf-ce193b74e5ef", "rel"=>"self"},
    {"href"=>"http://nova.exapmle.com:8080/b598bf98671c47e1b955f8c9660e3c44/servers/546143ff-7b7c-4423-9bcf-ce193b74e5ef", "rel"=>"bookmark"}],
  "name"=>"yum-test001.heteml.jp"},
 {"id"=>"c23d5cd8-7c79-4df0-a5a2-1f242bea56c1",
  "links"=>
   [{"href"=>"http://nova.exapmle.com:8080/v2/b598bf98671c47e1b955f8c9660e3c44/servers/c23d5cd8-7c79-4df0-a5a2-1f242bea56c1", "rel"=>"self"},
    {"href"=>"http://nova.exapmle.com:8080/b598bf98671c47e1b955f8c9660e3c44/servers/c23d5cd8-7c79-4df0-a5a2-1f242bea56c1", "rel"=>"bookmark"}],
  "name"=>"zipper-test.local"},
 {"id"=>"1f8733f7-2f49-4ee5-b633-9ac064e4274e",
  "links"=>
   [{"href"=>"http://nova.exapmle.com:8080/v2/b598bf98671c47e1b955f8c9660e3c44/servers/1f8733f7-2f49-4ee5-b633-9ac064e4274e", "rel"=>"self"},
    {"href"=>"http://nova.exapmle.com:8080/b598bf98671c47e1b955f8c9660e3c44/servers/1f8733f7-2f49-4ee5-b633-9ac064e4274e", "rel"=>"bookmark"}],
  "name"=>"ryoshin-proxy.exapmle.com"},
#...
]

そしてこれだけでGET系はかなりのリソースをカバーできるっぽいことを確認。

ただ、POST系はパラメータが多すぎてまだ動かせていない...スマートに書く方法を考えている。

さらに言うと、レスポンスはJSONをピュアにパースしただけのHashなので、もっとオブジェクトっぽく触れないと面倒くさそうだ。これも、いろんな形のJSONがあるんで、 OpenStruct 以上の良いソリューションを考えないといけない。


そんな感じでたまにこのシリーズを連載予定。

`soko(倉庫)` というサーバメタデータを取り扱うミドルウェアを作っている

github.com

みなさん、userdata書いてますか(挨拶)。

今日は、細々と書いてきた、自作ツールの紹介をしてみます。

どういうものか

続きを読む