LoginSignup
2
2

More than 5 years have passed since last update.

PryからSpecinfraを簡単に使うためのカスタムコマンド作ってみた

Last updated at Posted at 2015-01-25

PryからSpecinfraを簡単に使うためのカスタムコマンドを作成してみました。

背景

Specinfraを使うとOSの違いをあまり意識することなく設定の確認やパッケージのインストールといった操作を行うことができます。

以下のようにSpecinfra::Runner のメソッドを呼び出すことで使うことができます。

>> Specinfra::Runner.check_package_is_installed('nginx')
=> false

ライブラリから使うのには良いですが、Pryからちょっと使ってみたいというとき補完や定義を表示するのがしんどそうだったのでカスタムコマンドを作成してみました。

カスタムコマンドの導入

以下の内容を~/.pryrc に追加することでカスタムコマンドが使えるようになります。

Pry::Commands.create_command "add-specinfra-commands"  do
  description "Add Specinfra Commands"
  command_options :keep_retval => false

  banner <<-BANNER
    Usage:
      add-specinfra-commands
  BANNER

  def setup
    require 'specinfra'
  end

  def process
    Specinfra.configuration.send(:backend, :exec)
    os

    Pry::Commands.create_command 'specinfra-ssh' do
      description "Set Specinfra SSH parameters"
      command_options :keep_retval => false

      def options(opt)
        login_user = ENV['USER'] || ENV['LOGNAME'] || Etc.getlogin || Etc.getpwuid.name
        opt.on :l, 'Login Name', :default => login_user, :argument => true, :as => String
        opt.on :i, 'Identity file', :default => '~/.ssh/id_rsa',:argument => true, :as => String
        opt.on :p, 'Port', :default => 22, :argument => true, :as => Integer
      end

      def process
        Specinfra.configuration.send(:backend, :ssh)
        Specinfra.configuration.send(:host, args.first)
        Specinfra.configuration.send(
          :ssh_options, :user => opts[:l],
                        :port => opts[:p],
                        :keys => [File.expand_path(opts[:i])]
        )
        property[:os] = detect_os
      end
    end

    get_resource_types.each do |resouce_type|
      get_commands(resouce_type).each do |command, command_args|
        command_args = command_args.map { |a|
          case a.first
          when :opt  then "[#{a.last.to_s.upcase}]"
          when :rest then "[#{a.last.to_s.upcase} ...]"
          else a.last.to_s.upcase
          end
        }
        description = "Usage: specinfra-#{command.to_s.gsub(/_/,'-')} [OPTIONS] #{command_args.join(' ')}"
        target.eval <<-EOS
        Pry::Commands.create_command 'specinfra-#{command.to_s.gsub(/_/,'-')}' do
          description "#{description}"
          command_options :keep_retval => true

          def options(opt)
            opt.on :n, :dry, 'Dry run'
          end

          def process
            unless opts[:dry]
              Specinfra::Runner.send(:#{command.to_s}, *args)
            else
              Specinfra.command.get(:#{command.to_s}, *args)
            end
          end
        end
        EOS
      end
    end
  end

  private

  def get_resource_types
    Specinfra::Command::Base.subclasses.select { |c|
      c.to_s =~ /^Specinfra::Command::Base/
    }
    .sort { |a, b| a.to_s <=> b.to_s }
    .map { |c| c.to_s.split('::')[-1] }
  end

  def get_commands(resource_type)
    resource_type = resource_type.downcase
    base_class = Specinfra::Command::Base.subclasses.detect { |c|
      c.to_s.downcase =~ /^specinfra::command::base::#{resource_type}/
    }
    classes = [base_class]+ base_class.subclasses

    commands = {}
    classes.each do |c|
      c.methods(false).each do |m|
        action, sub_action = m.to_s.split('_', 2)
        name = unless sub_action.nil?
          "#{action}_#{resource_type}_#{sub_action}"
        else 
          "#{action}_#{resource_type}"
        end

        next if commands.has_key?(name)

        commands[name] = c.method(m).parameters
      end
    end
    commands
  end
end

使い方

pryを開始し次のコマンドを実行するとSpecinfra関連のコマンド群が追加されます。

>> add-specinfra-commands

追加されるコマンド群は大きく分けて以下の2つです。

  • specinfra-ssh
    • SSHの設定を行うためのコマンド
  • specinfra-{action}-{resource_type}-{subaction}
    • 各種アクションを実行するためのコマンド。Spec::Runnerのメソッドの_-に置換した名前となっています。

specinfra-ssh

specinfra-ssh は、SSHのオプションを簡単に設定するためのコマンドです。設定後にdetect_osproperty[:os] を更新します。

>> specinfra-ssh -l root -p 22 X.X.X.X
>> os
=> {:family=>"redhat", :release=>"7.0.1406", :arch=>"x86_64"}

specinfra-{action}-{resource_type}-{subaction}

これらのコマンド群は実際にコマンドを実行するためのコマンドです。

>> specinfra-check-package-is-installed python
=> true

-h でヘルプが表示されます。

>> specinfra-check-package-is-installed -h
Usage: specinfra-check-package-is-installed [OPTIONS] PACKAGE [VERSION]
    -n, --dry       Dry run
    -h, --help      Show this message.

-n で実行されるコマンドが表示されます。ただし、実行結果のrubyによる処理は考慮されません。

>> specinfra-check-package-is-installed -n python
=> "/usr/local/bin/brew list -1 | grep -E '^python$'"
>> ./usr/local/bin/brew list -1 | grep -E '^python$'
python

例:CentOS7にMariaDBをインストール

使用例としてDigitalOceanにCentOS7の仮想マシンを作成しMariaDBをインストールしてみます。

仮想マシンの作成は前回の記事と同じくFogを使いました。(pryとfogでお手軽マルチクラウド対応CLI)

>> fog digitalocean
>> region = regions.find { |r| r.name == "Singapore 1" }
>> image = images.find {|i| i.name == "7.0 x64" && i.distribution == "CentOS" }
>> flavor = flavors.find { |f| f.name == "512MB" }
>> ssh_key = ssh_keys.first
>> server = servers.create(:name=>"specinfra-test", :region_id=>region.id, :image_id=>image.id, :flavor_id=>flavor.id, :ssh_key_ids=>[ssh_key.id])
>> server.wait_for { ready? } 

Specinfraのコマンドを追加し、作成した仮想マシンへ接続するための情報を設定します。

>> add-specinfra-commands
>> specinfra-ssh -l root X.X.X.X
>> os
=> {:family=>"redhat", :release=>"7.0.1406", :arch=>"x86_64"}
>> specinfra-check-package-is-installed -n mariadb-server
=> "rpm -q mariadb-server"
>> specinfra-check-package-is-installed mariadb-server
=> false
>> specinfra-install-package mariadb-server
=> 長いので省略
>> specinfra-check-package-is-installed mariadb-server
=> true

>> specinfra-check-service-is-running -n mariadb
=> "systemctl is-active mariadb"
>> specinfra-check-service-is-running mariadb
=> false
>> specinfra-start-service -n mariadb
=> "systemctl start mariadb"
>> specinfra-start-service mariadb
=> #<Specinfra::CommandResult:0x007f912c9a24e0 @exit_signal=nil, 
@exit_status=0, @stderr="", @stdout="">
>> specinfra-check-service-is-running mariadb
=> true

>> specinfra-check-service-is-enabled -n mariadb
=> "systemctl --plain list-dependencies multi-user.target | grep '\\(^\\| \\)mariadb.service$'"
>> specinfra-check-service-is-enabled  mariadb
=> false
>> specinfra-enable-service -n  mariadb
=> "systemctl enable mariadb"
>> specinfra-enable-service  mariadb
=> #<Specinfra::CommandResult:0x007f9129bd6570
 @exit_signal=nil,
 @exit_status=0,
 @stderr="ln -s '/usr/lib/systemd/system/mariadb.service' '/etc/systemd/system/multi-user.target.wants/mariadb.service'\n",
 @stdout="">
>> specinfra-check-service-is-enabled  mariadb
=> true

課題

作成するにあたって色々とハマりましたが特に以下の2つはまだ解決していません。もし知っている方がいたら教えて頂けるとありがたいです。

  • eval族を使用している際にPryでソースコードを追う方法・コツ
  • メソッドの引数のデフォルト値を取得する方法

参考

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2