commit 21f1be78d69613a1b52a53584d13abe800923721 Author: Jim Myhrberg Date: Thu Dec 10 19:09:00 2009 +0200 initial import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ca0973 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store + diff --git a/lib/skyline/console.rb b/lib/skyline/console.rb new file mode 100644 index 0000000..8505b8f --- /dev/null +++ b/lib/skyline/console.rb @@ -0,0 +1,168 @@ +def load_config + Skyline.load_config +end + +def init + Skyline.init +end + +def reload + Skyline.init(true) +end + +def list(what = nil) + case what + when :all + Skyline.as_groups.each do |name, group| + puts name.to_s + group[:instances].each do |instance| + puts " #{instance[:id].ljust(10)} " + + "#{instance[:public_ip].ljust(15)} " + + "#{instance[:private_ip].ljust(15)} " + + "#{instance[:ami].ljust(10)} " + + "#{instance[:type].ljust(10)} " + + "#{instance[:state].ljust(15)} " + + "#{instance[:launch_date].ljust(15)}" + end + end + when :instances + Skyline.instances.each do |i, instance| + puts " #{instance[:id].ljust(10)} " + + "#{instance[:public_ip].ljust(15)} " + + "#{instance[:private_ip].ljust(15)} " + + "#{instance[:ami].ljust(10)} " + + "#{instance[:type].ljust(10)} " + + "#{instance[:status].ljust(15)} " + + "#{instance[:launch_date].ljust(15)}" + end + else + Skyline.as_groups.each { |name, group| puts name.to_s } + end + return +end + +def selected + puts Skyline.selected +end + +def cmd(command) + Skyline.cmd(command) +end + +def icmd + if Skyline.selected.nil? + puts "ERROR: No AS Group selected. Please use" + puts "the \"use\" command to select one." + return nil + end + exit = false + started = false + while !exit + if !started + puts "Entering interactive remote-terminal mode." + puts "" + puts "All entered commands are executed on all remote" + puts "instances within selected auto-scaling group." + puts "" + puts "Type \"exit\"\" or \"halt\"\" to exit IRT mode." + puts "" + started = true + end + print "irt$ " + input = STDIN.readline.strip + if !input.nil? && input != "" + if input =~ /^exit|halt$/ + exit = true + else + cmd(input) + end + end + end +end + +def use + groups = ["All"] + Skyline.as_groups.map { |name, group| name } + valid_choice = false + while !valid_choice + puts "Please choose a group to use:" + groups.each_with_index { |group, i| puts "#{i}. #{group}" } + print "Enter your choice (0-9) or press Ctrl-C to abort: " + s = STDIN.readline.strip + s = (s =~ /^[\d]+$/) ? s.to_i : nil + if !s.nil? && !groups[s].nil? + valid_choice = true + groups[0] = :all if s == 0 + puts groups[s].inspect + return Skyline.use(groups[s]) + else + puts "\nERROR: Not a valid choice\n\n" + end + end +end + +def help_me + cmds = [ + [:reload, "Force-reload instance and AutoScaling Group info."], + [:list, <<-hd +List loaded information. Options are: + :all List AS Groups with instances. + :instances List all instances in your EC2 account. +hd +], + [:use, "Call without any arguments to choose which AS Group to work with."], + [:cmd, "Run a command on all selected remote machines."], + [:icmd, "Enter an interactive shell which runs entered commands on selected remote machines."] + ] + puts "Available Skyline commands:" + cmds.each do |cmd, desc| + print " #{cmd.to_s.ljust(10)} " + desc = desc.split("\n") + if desc.size == 1 + puts desc[0] + else + puts desc.shift + desc.each do |line| + puts " #{line}" + end + end + end + return +end + + +# +# Skyhook related commands +# + +def skyhook(command) + cmd("skyhook #{command}") +end + +def update(project = nil, rev = nil) + if project.nil? + puts "Please specify a project." + return + end + rev = rev.gsub(/[^0-9]/, "") if rev.is_a?(String) + commands = [ + [ "skyhook update", + "Updating Skyhook on remote machines..." ], + [ "skyhook update.#{project} #{rev}", + "Fetching latest files for #{project} project on remote machines..." ], + [ "skyhook mode +maintenance", + "Putting remote machines in maintenance mode..." ], + [ "skyhook activate.#{project} #{rev}", + "Activating newly fetched version of #{project} on remote machines..." ], + [ "skyhook mode -maintenance", + "Disabling maintenance mode on remote machines" ] + ] + commands.each do |command| + puts command[1] if !command[1].nil? + if !command[0].nil? + puts " Executing: #{command[0]}" + cmd command[0] + end + end + return +end + diff --git a/lib/skyline/skyline.rb b/lib/skyline/skyline.rb new file mode 100644 index 0000000..48b888d --- /dev/null +++ b/lib/skyline/skyline.rb @@ -0,0 +1,182 @@ +require "rubygems" +require "yaml" +require "scanf" + +class Skyline + + @as_groups = {} + @instances = {} + @selected = nil + @config = {} + + class << self + attr_accessor :as_groups + attr_accessor :instances + attr_accessor :selected + attr_accessor :config + end + + def self.load_config + if File.exist?("skyline_config.yml") + @config = YAML.load_file("skyline_config.yml") + return true + end + return false + end + + def self.init(force = false) + get_instances if @instances.size == 0 || force + get_as_groups if @as_groups.size == 0 || force + return + end + + def self.use(group = nil) + if !group.nil? + if @as_groups.has_key?(group.to_s) + @selected = group.to_s + puts "Selected AutoScaling group: #{group}" + return true + elsif group == :all + @selected = :all + puts "NOTICE: You have selected all AutoScaling groups" + return true + else + puts "ERROR: AutoScaling group #{group} doesn't seem to exist" + return false + end + end + end + + def self.cmd(command) + ssh_key = "" + ssh_key = "-i " + @config["ssh_key"] if @config.has_key?("ssh_key") && !@config["ssh_key"].nil? + template = "ssh #{ssh_key} root@{REMOTE_IP} " + + "\"export PATH=\\\"/opt/ruby-enterprise/bin:$PATH\\\"; {CMD}\"" + cmds = [] + if @selected.nil? + puts "ERROR: No AS Group selected." + return + end + @as_groups.each do |name, group| + if @selected.to_s == name.to_s || @selected == :all + group[:instances].each do |instance| + if instance[:state] == "InService" && !instance[:public_ip].nil? + cmds << template.gsub("{REMOTE_IP}", instance[:public_ip]).gsub("{CMD}", command.gsub(/(")/, "\\\\\\1")) + end + end + end + end + if cmds.size > 0 + puts "\nRunning command on all machines" + self.call_parallel(cmds) do |results| + results.each do |result| + puts "------------------------------------------" + puts " Command: #{result[0]}" + puts " Exit code: #{result[2].to_i}" + puts " Output: " + puts "#{result[1]}" + end + puts "------------------------------------------" + end + else + puts "No machines available under current AS Group." + end + return + end + + def self.call_parallel(cmds = [], check_interval = nil) + if cmds.size > 0 + threads, results = [], [] + cmds.each do |cmd| + threads << Thread.new { results << [cmd, `#{cmd}`, $?] } + end + is_done = false + check_interval = 0.2 if check_interval.nil? + while is_done == false do + sleep check_interval + done = true + threads.each { |thread| done = false if thread.status != false } + is_done = true if done + end + yield(results) + end + end + + def self.get_instances + print "Loading EC2 instance information. " + raw = `ec2-describe-instances` + if raw =~ /service error/i + puts "ERROR: EC2 Service error." + return false + end + lines = raw.split("\n") + lines.each do |line| + line = line.strip.split(/\s/) + if line[0].downcase == "instance" && !line[5].nil? && line[5].downcase == "running" + @instances[line[1]] = { + :id => line[1], :ami => line[2], :public_dns => line[3], :private_dns => line[4], + :status => line[5], :ssh_key => line[6], :idx => line[7], :type => line[9], + :launch_date => line[10], :zone => line[11], :aki => line[12], :ari => line[13], + :monitoring => (line[15] == "monitoring-enabled") ? true : false, + :public_ip => line[16], :private_ip => line[17], + } + end + end + puts "DONE." + return + end + + def self.get_as_groups + print "Loading EC2 AutoScaling Groups information. " + if @instances.size == 0 + puts "ERROR: Instances not loaded, or no running instances." + return false + end + raw = `as-describe-auto-scaling-groups` + if raw =~ /service error/i + puts "ERROR: EC2 Service error" + return false + end + raw = raw.strip.split("AUTO-SCALING-GROUP") + raw.delete("") + raw.each do |group| + lines = group.split("\n") + head = lines.shift.strip.split(/\s+/) + instances = [] + lines.each do |line| + line = line.split(/\s+/) + instance = { + :id => line[1], + :group => line[2], + :zone => line[3], + :state => line[4], + :public_ip => nil, :private_ip => nil, :ami => nil, :type => nil, + :status => nil, :ssh_key => nil, :public_dns => nil, + :private_dns => nil, :launch_date => nil + } + if !@instances[line[1]].nil? + instance = instance.merge({ + :public_ip => @instances[line[1]][:public_ip], + :private_ip => @instances[line[1]][:private_ip], + :ami => @instances[line[1]][:ami], + :type => @instances[line[1]][:type], + :status => @instances[line[1]][:status], + :ssh_key => @instances[line[1]][:ssh_key], + :public_dns => @instances[line[1]][:public_dns], + :private_dns => @instances[line[1]][:private_dns], + :launch_date => @instances[line[1]][:launch_date] + }) + end + instances << instance + end + @as_groups[head[0]] = { + :name => head[0], :launch_config => head[1], :zone => head[2], :load_balancer => head[3], + :min_size => head[4], :max_size => head[5], :current_size => head[6], + :instances => instances + } + end + puts "DONE." + return + end + +end \ No newline at end of file diff --git a/lib/skyline_console.rb b/lib/skyline_console.rb new file mode 100644 index 0000000..a27e541 --- /dev/null +++ b/lib/skyline_console.rb @@ -0,0 +1,14 @@ +require "skyline/skyline" +require "skyline/console" + +load_config +# init + +puts <<-hd + +Welcome to the Skyline console. + +Available commands are: +list, use, cmd, icmd, reload and help_me. + +hd \ No newline at end of file diff --git a/skyline b/skyline new file mode 100755 index 0000000..0d81546 --- /dev/null +++ b/skyline @@ -0,0 +1,2 @@ +#! /usr/bin/env ruby +exec "irb -rubygems -I lib --simple-prompt -r skyline_console.rb" \ No newline at end of file diff --git a/skyline_config.yml b/skyline_config.yml new file mode 100644 index 0000000..0ef9430 --- /dev/null +++ b/skyline_config.yml @@ -0,0 +1 @@ +ssh_key: ~/.ec2/kp_jimeh_default \ No newline at end of file