require 'engineyard-serverside/server' require 'forwardable' require 'set' require 'engineyard-serverside/spawner' module EY module Serverside class Servers class DuplicateHostname < StandardError def initialize(hostname) super "EY::Serverside::Server '#{hostname}' duplicated!" end end # Array compatibility extend Forwardable include Enumerable def_delegators :@servers, :each, :size, :empty? def select(*a, &b) self.class.new @servers.select(*a,&b), @shell end def reject(*a, &b) self.class.new @servers.reject(*a,&b), @shell end def to_a() @servers end def ==(other) other.respond_to?(:to_a) && other.to_a == to_a end def self.from_hashes(server_hashes, shell) servers = server_hashes.inject({}) do |memo, server_hash| server = Server.from_hash(server_hash) raise DuplicateHostname.new(server.hostname) if memo.key?(server.hostname) memo[server.hostname] = server memo end new(servers.values, shell) end def initialize(servers, shell) @servers = servers @shell = shell @cache = {} end def localhost @servers.find {|server| server.local? } end def remote reject { |server| server.local? } end # We look up the same set of servers over and over. # Cache them so we don't have to find them every time # Accepts a block (because it's confusing when you send a block to this # method and it doesn't yield and it's better than raising) def roles(*select_roles, &block) if block_given? return yield(roles(*select_roles)) end roles_set = Set.new select_roles.flatten.compact.map{|r| r.to_sym} if roles_set.empty? || roles_set.include?(:all) self else @cache[roles_set] ||= select { |server| server.matches_roles?(roles_set) } end end def run_on_each(cmd, &block) run_for_each do |server| server.command_on_server('sh -l -c', cmd, &block) end end # Run a command on this set of servers. def run_on_each!(cmd, &block) run_for_each! do |server| server.command_on_server('sh -l -c', cmd, &block) end end alias run run_on_each! # Run a sudo command on this set of servers. def sudo_on_each(cmd, &block) run_for_each do |server| server.command_on_server('sudo sh -l -c', cmd, &block) end end # Run a sudo command on this set of servers. def sudo_on_each!(cmd, &block) run_for_each! do |server| server.command_on_server('sudo sh -l -c', cmd, &block) end end alias sudo sudo_on_each! def run_for_each(&block) spawner = Spawner.new each { |server| spawner.add(block.call(server), @shell, server) } spawner.run end def run_for_each!(&block) failures = run_for_each(&block).reject {|result| result.success? } if failures.any? commands = failures.map { |f| f.command }.uniq servers = failures.map { |f| f.server }.compact.map { |s| s.inspect } outputs = failures.map { |f| f.output }.uniq message = "The following command#{commands.size == 1 ? '' : 's'} failed" if servers.any? message << " on server#{servers.size == 1 ? '' : 's'} [#{servers.join(', ')}]" end message << "\n\n" commands.each do |cmd| message << "$ #{cmd}\n" end message << "\n" << outputs.join("\n\n") << "\n" raise EY::Serverside::RemoteFailure.new(message) end end end end end