#encoding: utf-8 require 'thor' require 'chef/knife/core/ui' require 'vagabond/uploader' require 'elecksee/lxc' %w(constants errors vagabondfile internal_configuration helpers).each do |dep| require "vagabond/#{dep}" end Dir.glob( File.join( File.dirname(__FILE__), 'actions', '*.rb' ) ).each do |action| require "vagabond/actions/#{File.basename(action).sub('.rb', '')}" end module Vagabond class Vagabond < Thor DISABLE_HOST_SOLO_ON = %w(status init) include Thor::Actions include Helpers Actions.constants.each do |const| klass = Actions.const_get(const) include klass if klass.is_a?(Module) end attr_accessor :mappings_key attr_accessor :lxc attr_accessor :config attr_accessor :action CLI_OPTIONS = lambda do class_option(:debug, :type => :boolean, :default => false ) class_option(:force_solo, :aliases => '--force-configure', :type => :boolean, :default => false, :desc => 'Force configuration of system' ) class_option(:color, :type => :boolean, :default => true, :desc => 'Enable/disable colorized output' ) class_option(:vagabond_file, :aliases => '-f', :type => :string, :desc => 'Provide path to Vagabondfile' ) class_option(:local_server, :type => :boolean, :default => true, :desc => 'Enable/disable local Chef server usage if available' ) class_option(:callbacks, :type => :boolean, :default => true, :desc => 'Enable/disable action callbacks' ) end CLI_OPTIONS.call # action:: Action to perform # name:: Name of vagabond # config:: Hash configuration # # Creates an instance def initialize(*args) super @threads = Mash.new @mappings_key = :mappings end ## COMMANDS COMMANDS = lambda do |show_node=true| Actions.constants.find_all do |const| Actions.const_get(const).is_a?(Module) end.map(&:to_s).map(&:downcase).each do |meth| if(self.respond_to?("_#{meth}_desc")) args = self.send("_#{meth}_desc") else args = ["#{meth}#{' NODE' if show_node}", "#{meth.capitalize} instance#{' of NODE' if show_node}"] end desc(*args) if(self.respond_to?("_#{meth}_options")) self.send("_#{meth}_options").each do |opts| method_option(*opts) end end define_method meth do |*args| @original_args = args.dup unless(args.include?(:no_setup)) setup(meth, *args) end result = execute callbacks(meth) chain! result end end end COMMANDS.call protected def attributes if(config[:attributes]) if(config[:attributes].is_a?(Hash)) JSON.dump(config[:attributes]) else config[:attributes].to_s end end end def version setup_ui ui.info "#{ui.color('Vagabond:', :yellow, :bold)} - Advocating idleness and work-shyness" ui.info " #{ui.color('Version:', :blue)} - #{VERSION.version} (#{VERSION.codename})" exit end def execute self.send("_#{action}") end def setup(action, name=nil, *args) @action = action @name = name hash_args = args.detect{|x|x.is_a?(Hash)} if(hash_args) args.delete(hash_args) _ui = hash_args.delete(:ui) base_setup(_ui) config.merge!(hash_args) else base_setup end @leftover_args = args end def name_required! unless(name) ui.fatal "Node name is required!" raise VagabondError::MissingNodeName.new end end def provision_solo(dir) ui.info "#{ui.color('Vagabond:', :bold)} Provisioning node: #{ui.color(name, :magenta)}" lxc.container_ip(20) # force wait for container to appear and do so quietly cmd = direct_container_command( "chef-solo -c #{File.join(dir, 'solo.rb')} -j #{File.join(dir, 'dna.json')}", :live_stream => STDOUT ) raise VagabondError::NodeProvisionFailed.new("Failed to provision: #{name}") unless cmd end def validate! if(name.to_s == 'server') ui.fatal "RESERVED node name supplied: #{ui.color(name, :red)}" ui.info ui.color(" -> Try: vagabond server #{action}", :cyan) raise VagabondError::ReservedName.new(name) end if(name && config.nil? && !options[:disable_name_validate]) ui.fatal "Invalid node name supplied: #{ui.color(name, :red)}" ui.info ui.color(" -> Available: #{vagabondfile[:nodes].keys.sort.join(', ')}", :cyan) raise VagabondError::InvalidName.new(name) end end def check_existing! if(lxc.exists?) ui.error "LXC: #{name} already exists!" true end end def base_dir File.dirname(vagabondfile.path) end def vagabond_dir File.join(base_dir, '.vagabond') end def wait_for_completion(type=nil) @threads ||= [] if(type) Array(@threads[type]).collect{|hsh| hsh[:thread]}.map(&:join) else @threads.values.flatten.collect{|hsh| hsh[:thread]}.map(&:join) end end def tasks(type=nil) type ? @threads[type] : @threads end end end