tl;dr
皆さん, ステイホームしてますか.
ちょっと前に久しぶりに EC2 のカスタム AMI を作成するにあたって, Vagrant + vagrant-ec2 + mitamae + goss という組み合わせで実装したりテストをしていました. mitamae でレシピを流して, goss でテストをするという流れです.
mitamae も goss も Vagrant の Shell Provisioner で以下のように実行することが出来るのですが, 以下のように goss だけ走らせたい時に面倒だなあという思いがあり Vagrant Plugin 実装の勉強も兼ねて Provisioner プラグインを作ってみました.
$ vagrant provision --provision-with=goss
vagrant-plugin-goss
リポジトリ
Vagrantfile
goss は Golang で書かれている為, ワンバイナリ化されています. これをダウンロードする為に wget
コマンドは必須です.
Vagrant.configure('2') do |config| config.vm.box = 'centos/7' config.vm.provider "virtualbox" do |vb| vb.memory = '512' end config.vm.synced_folder '.', '/vagrant', disabled: false, type: 'rsync', rsync__exclude: [ '.envrc', '.ruby-version', '.env', './tmp/', './bk/' ] config.vm.provision :shell, inline: <<~BASH sudo yum install -y wget httpd BASH config.vm.provision :goss do |goss| goss.root_path = '/vagrant' # root_path からの絶対パスで指定する #{root_path}/foo/bar であれば, /foo/bar と指定 goss.spec_file = '/spec/goss.yaml' # root_path からの絶対パスで指定する #{root_path}/baz であれば, /baz と指定 # goss.vars_file = '/vars.yaml' # root_path からの絶対パスで指定する #{root_path}/foo/bar であれば, /foo/bar と指定 goss.goss_path = '/goss' # goss の output format を指定する, デフォルトは documentation, junit, json, nagios, rspecish, tap, silent # goss.output_format = 'junit' end end
後は, 任意のディレクトリに goss が利用する YAML ファイル (--gossfile
オプションで指定するファイル) を用意します.
$ mkdir spec $ cat << EOF > ./spec/goss.yaml package: httpd: installed: true wget: installed: true EOF
後は, vagrant up
からの vagrant provision
や vagrant rsync
からの vagrant provision
等, 素敵な vagrant 生活をお送り下さい.
vagrant-plugin-goss 前夜
ちなみに, vagrant-plugin-goss を作る前は, 以下のように Shell Provisioner を書いておりました.
config.vm.provision :shell, inline: <<~BASH cd /vagrant && \ ./bin/depend.sh && \ ./bin/mitamae local bootstrap.rb --node-yaml=node.yml #{args} ./bin/goss --gossfile spec/goss.yaml --vars node.yml validate --format documentation BASH
この状態で mitamae だけ, goss だけ実行したい場合, いちいち該当行以外をコメントアウトしていました.
Vagrant Plugin の実装について
以下は
このプラグインを作っていく上で学んだことなどを書きます. あくまでも自分の主観で書いているので, 用語の使い方等に誤りがあるかもしれませんがご容赦下さい.
参考
以下のドキュメントや Github リポジトリを参考にしました.
plugin.rb が起点になる
以下は lib/vagrant-goss/plugin.rb
の抜粋です.
module VagrantPlugins module Goss class Plugin < Vagrant.plugin('2') name 'goss' description <<-DESC This plugin executes a goss suite against a running Vagrant instance. DESC config(:goss, :provisioner) do require_relative 'config' Config end provisioner(:goss) do require_relative 'provisioner' Provisioner end end end end
プラグインにとって, ここが起点になり, 今回は Provisioner プラグインとなるので, config
と provisioner
のブロックを定義します. 個々のブロックで require_relative
の引数に指定している config
や provisioner
をコツコツと書いていくことになります.
ちなみに config
や provisioner
以外にも command
等も用意されています. commandについては,
vagrant xxxx` と実行する際に実行されるコマンド等を定義出来るのかなーと予想しています.
config.rb
以下は lib/vagrant-goss/config.rb
の抜粋です.
module VagrantPlugins module Goss class Config < Vagrant.plugin('2', :config) attr_accessor :output_format attr_accessor :spec_file ... 略 ... def initialize super @output_format = UNSET_VALUE @spec_file = UNSET_VALUE ... 略 ... def finalize! @output_format = 'documentation' if @output_format == UNSET_VALUE @spec_file = 'goss.yaml' if @spec_file == UNSET_VALUE ... 略 ...
attr_accessor
しているのは, Vagrantfile で以下のように (goss.root_path
とか) 書くためですよね. (多分
config.vm.provision :goss do |goss| goss.root_path = '/vagrant' # root_path からの絶対パスで指定する #{root_path}/foo/bar であれば, /foo/bar と指定 goss.spec_file = '/spec/goss.yaml' ... 略 ...
provisioner.rb
以下は lib/vagrant-goss/provisioner.rb
の抜粋です.
... 略 ... module VagrantPlugins module Goss class Provisioner < Vagrant.plugin('2', :provisioner) ... 略 ... def provision vars_option = "--vars #{@vars_file} " unless @vars_file.nil? run = "#{@goss_path} --gossfile #{@spec_file} " + "#{vars_option} " + "validate " + "--format #{@output_format} --color" run_command(run) if download_goss(@goss_path) end
goss 実行時の出力を Vagrant 標準の出力 (以下のように, 標準出力の場合には緑で表示されたり, default:
が行頭につくようにする) にする為に試行錯誤しました.
結局, Vagrant Shell Provisioner のソースコードを参考 (そのまま利用) させて頂きました. 以下の部分です.
# refer to: https://github.com/hashicorp/vagrant/blob/master/plugins/provisioners/shell/provisioner.rb#L67-L81 def handle_comm(type, data) if [:stderr, :stdout].include?(type) # Output the data with the proper color based on the stream. color = type == :stdout ? :green : :red # Clear out the newline since we add one data = data.chomp return if data.empty? options = {} options[:color] = color if !config.keep_color machine.ui.detail(data.chomp, options) end end
ドキュメントにも書かれていました.
Most plugins are likely going to want to do some sort of input/output. Plugins should never use Ruby's built-in puts or gets style methods. Instead, all input/output should go through some sort of Vagrant UI object. The Vagrant UI object properly handles cases where there is no TTY, output pipes are closed, there is no input pipe, etc.
- puts 等の Ruby 標準の入出力メソッドは使ってはいけない
- 代わりに Vagrant が提供している UI オブジェクトを利用する必要がある
- UI オブジェクトは
ui
プロパティを介して, すべてのVagrant::Environment
で使用することが出来る
以上
ステイホームで素敵なインフラストラクチャアズコードな生活をお送り下さい.