judo 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -10,8 +10,7 @@ Jeweler::Tasks.new do |s|
10
10
  s.rubyforge_project = "judo"
11
11
  s.files = FileList["[A-Z]*", "{bin,lib,spec}/**/*"]
12
12
  s.executables = %w(judo)
13
- s.add_dependency "aws", [">= 2.3.1"]
14
- s.add_dependency "thor", [">= 0.13.4"]
13
+ s.add_dependency "aws", [">= 2.3.5"]
15
14
  s.add_dependency "json"
16
15
  end
17
16
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.9
1
+ 0.1.0
data/bin/judo CHANGED
@@ -1,217 +1,107 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require File.dirname(__FILE__) + '/../lib/all'
4
-
5
- require 'thor'
6
-
7
- class CLI < Thor
8
- desc "start [NAMES]", "start one or more servers"
9
- def start(*names)
10
- servers(*names) do |server|
11
- server.start
12
- end
13
- end
14
-
15
- desc "dump", "start one or more servers"
16
- def dump
17
- require 'pp'
18
- pp Judo::Config.sdb.select("SELECT * FROM judo_servers")[:items]
19
- pp Judo::Config.sdb.select("SELECT * FROM judo_config")[:items]
20
- end
21
-
22
- desc "ssh [NAMES]", "ssh to a specified server or first available"
23
- def ssh(*names)
24
- servers(*names) do |server|
25
- server.reload
26
- server.wait_for_ssh
27
- server.connect_ssh
28
- end
29
- end
30
-
31
- desc "launch NAME", "create and start a persistent server"
32
- def launch(name)
33
- create(name, true)
34
- end
35
-
36
- desc "create NAME", "create a persistent server"
37
- def create(name, start = false)
38
- group = get_current
39
- if name =~ /^[+](\d*)/
40
- n = $1.to_i
41
- abort "woah nelly - that's too many - 5 or less pls" if n > 5
42
- abort "woah nelly - that's not enough" if n < 1
43
- top_counter = group.servers.map { |s| (s.name =~ /^#{s.group}.(\d*)$/); $1.to_i }.sort.last.to_i
44
- n.times do |i|
45
- top_counter += 1
46
- server = group.create_server "#{group}.#{top_counter}"
47
- server.allocate_resources
48
- server.start if start
49
- end
50
- else
51
- server = group.create_server name
52
- server.allocate_resources
53
- server.start if start
54
- end
55
- end
56
-
57
- desc "restart NAME", "restart a running server"
58
- def restart(*names)
59
- servers(*names) do |server|
60
- server.restart
61
- end
62
- end
63
-
64
- desc "stop [NAMES]", "stop a persistent server"
65
- def stop(*names)
66
- servers(*names) do |server|
67
- server.stop
68
- server.destroy if server.generic?
69
- end
70
- end
71
-
72
- desc "destroy NAMES", "destroy a persistent server"
73
- def destroy(*names)
74
- raise "Must specify names of servers to destroy" if names.empty?
75
- servers(*names) do |server|
76
- server.destroy
77
- end
78
- end
79
-
80
- desc "info [NAMES]", "show server config"
81
- def info(*names)
82
- servers(*names) do |server|
83
- require 'pp'
84
- puts "#{server}"
85
- if server.ec2_instance and not server.ec2_instance.empty?
86
- puts "\t[ EC2 ]"
87
- [:aws_instance_id, :ssh_key_name, :aws_availability_zone, :aws_state, :aws_image_id, :dns_name, :aws_instance_type, :private_dns_name, :aws_launch_time, :aws_groups ].each do |k|
88
- printf "\t %-24s: %s\n",k, server.ec2_instance[k]
89
- end
90
- end
91
- puts "\t[ VOLUMES ]"
92
- server.ec2_volumes.each do |v|
93
- printf "\t %-13s %-10s %-10s %4d %-10s %-8s\n",
94
- v[:aws_id],
95
- v[:aws_status],
96
- v[:zone],
97
- v[:aws_size],
98
- v[:aws_attachment_status],
99
- v[:aws_device]
100
- end
101
- # pp ({ :name => server.name , :group => server.group, :volumes => server.volumes, :hostname => server.hostname })
102
- # pp server.volumes.inspect
103
- # puts server.state.inspect
104
- ## EC2 describe_volumes
105
- # puts " #{dev.to_s}:#{Judo::Config.ec2.describe_volumes([vol_id])}"
106
- # end
107
- end
108
- end
109
-
110
- desc "list [NAMES]", "list all servers"
111
- def list(*names)
112
- if group = Judo::Group.current
113
- printf " SERVER IN GROUP #{group.name}\n"
114
- servers(*names) do |s|
115
- printf "%-18s %-4s %-11s %-11s %-13s %-10s %-10s %s\n", s.name, s.version_desc, s.state["instance_id"], s.instance_size, s.ami, s.ec2_state, "#{s.volumes.keys.size} volumes", s.ip
116
- end
117
- else
118
- printf " SERVER GROUPS\n"
119
- Judo::Group.all.each do |g|
120
- printf "%-18s %s servers\n", g.name, g.servers.size
121
- end
122
- # printf " UNGROUPED SERVERS\n"
123
- # servers.each do |s|
124
- # printf "%-18s %-11s %-11s %-13s %-10s %-10s %s\n", s.name, s.state["instance_id"], s.state["security_group"], s.ami, s.ec2_state, "#{s.volumes.keys.size} volumes", s.ip
125
- # end
126
- # else
127
- end
128
- end
129
-
130
- desc "console [NAMES]", "get console output for server or first available"
131
- def console(*names)
132
- servers(*names) do |server|
133
- puts server.console_output
134
- end
135
- end
136
-
137
- desc "init", "create a new judo repository in the current directory"
138
- def init
139
- Judo::Setup.new.init
140
- end
141
-
142
- desc "volumes", "list all volumes"
143
- def volumes
144
- format = "%13s %6s %12s %-10s %-16s %-16s\n"
145
- printf format, "AWS_ID", "SIZE", "AWS_STATUS", "AWS_DEVICE", "ATTACHED TO", "CONFIGURED FOR"
146
- printf "%s\n", ("-" * 80)
147
- ### EC2 describe_volumes
148
- Judo::Config.ec2.describe_volumes.map do |volume|
149
- [ volume[:aws_id], volume[:aws_size], volume[:aws_status], volume[:aws_device] || "", instance_id_to_judo(volume[:aws_instance_id]) || volume[:aws_instance_id] || "", volume_id_to_judo(volume[:aws_id]) ]
150
- end.sort { |a,b| [ a[5].to_s, a[3].to_s ] <=> [ b[5].to_s, b[3].to_s ] }.each do |d|
151
- printf format, *d
152
- end
153
- end
154
-
155
- desc "ips", "list all ips"
156
- def ips
157
- format = "%15s %20s %20s\n"
158
- printf format, "IP", "ATTACHED TO", "CONFIGURED FOR"
159
- printf "%s\n", ("-"*57)
160
- ## EC2 describe_addresses
161
- Judo::Config.ec2.describe_addresses.map do |ip|
162
- [ ip[:public_ip], instance_id_to_judo(ip[:instance_id]) || ip[:instance_id], ip_to_judo(ip[:public_ip]) ]
163
- end.sort { |a,b| a[2].to_s <=> b[2].to_s }.each do |d|
164
- printf format, *d
165
- end
166
- end
167
-
168
- desc "commit", "push configs and files to couchdb"
169
- def commit
170
- get_current.compile
171
- end
3
+ require 'optparse'
4
+ require File.dirname(__FILE__) + '/../lib/judo'
5
+ require File.dirname(__FILE__) + '/../lib/judo/commandline_helpers'
6
+
7
+ include JudoCommandLineHelpers
8
+
9
+ defaults = Judo::Base.default_options(Dir.pwd)
10
+
11
+ options = {}
12
+
13
+ action = ARGV.shift
14
+
15
+ ARGV.unshift "-h" unless action
16
+
17
+ optparse = OptionParser.new do|opts|
18
+ opts.banner = <<banner
19
+ Usage: judo launch [options] SERVER ...
20
+ judo create [options] SERVER ...
21
+ judo destroy [options] SERVER ...
22
+
23
+ # SERVER can be formatted as NAME or NAME:GROUP or +N or +N:GROUP
24
+ # where N is the number of servers to create or launch
25
+ # 'launch' only differs from 'create' in that it immediately starts the server
26
+
27
+ judo start [options] [SERVER ...]
28
+ judo stop [options] [SERVER ...]
29
+ judo restart [options] [SERVER ...]
30
+
31
+ judo commit [options] GROUP
32
+
33
+ judo info [options] [SERVER ...]
34
+ judo console [options] [SERVER ...] ## shows AWS console output
35
+ judo ssh [options] [SERVER ...] ## ssh's into the server
36
+
37
+ # SERVER can be formatted as NAME or NAME:GROUP
38
+ # or :GROUP to indicate the whole group.
39
+ # If no servers are listed all servers are assumed.
40
+
41
+ judo list [options] ## lists all servers
42
+ judo groups [options] ## lists all groups
172
43
 
173
- no_tasks do
174
- def servers(*names, &block)
175
- group = get_current
176
- good_servers = group.servers.select { |s| names.empty? or names.include?(s.name) }
177
- bad_names = (names - good_servers.map(&:name))
178
- abort "bad server name: #{bad_names.join(', ')}" unless bad_names.empty?
179
- good_servers.each do |server|
180
- begin
181
- block.call(server)
182
- rescue Object => e
183
- puts "Error on #{server.name}: #{e.message}"
184
- end
185
- end
186
- end
187
-
188
- def task(msg, &block)
189
- printf "---> %-24s ", "#{msg}..."
190
- start = Time.now
191
- result = block.call || 'done'
192
- result = "done" unless result.is_a? String
193
- finish = Time.now
194
- time = sprintf("%0.1f", finish - start)
195
- puts "#{result} (#{time}s)"
196
- result
197
- end
198
-
199
- def get_current
200
- Judo::Group.current || abort("This command must be run on a group - change into a group folder directory (such as 'default')")
201
- end
202
-
203
- def volume_id_to_judo(volume)
204
- Judo::Server.all.detect { |s| s.volumes.invert[volume] }
205
- end
206
-
207
- def ip_to_judo(ip)
208
- Judo::Server.all.detect { |s| s.elastic_ip == ip }
209
- end
210
-
211
- def instance_id_to_judo(instance_id)
212
- Judo::Server.all.detect { |s| s.instance_id and s.instance_id == instance_id }
213
- end
214
- end
44
+ judo volumes [options] ## shows all EBS volumes and what they are attached to
45
+ judo ips [options] ## shows all elastic ips and what they are attached to
46
+
47
+ banner
48
+
49
+ # start stop create destroy launch restart info console
50
+ # list volumes ips
51
+
52
+ opts.on( '-c', '--config DIR', 'Specify the location of the config dir' ) do |dir|
53
+ defaults[:judo_dir] = dir
54
+ defaults.merge(Judo::Base.default_options(Dir.pwd, dir))
55
+ end
56
+ opts.on( '-r', '--repo DIR', 'Specify the location of the repo dir' ) do |dir|
57
+ options[:repo] = dir
58
+ end
59
+ opts.on( '-a', '--accessid ID', 'Specify the AWS access ID' ) do |id|
60
+ options[:access_id] = id
61
+ end
62
+ opts.on( '-s', '--secret KEY', 'Specify the AWS access secret key' ) do |key|
63
+ options[:access_key] = key
64
+ end
65
+ opts.on( '-b', '--bucket BUCKET', 'Specify the AWS S3 bucket to use' ) do |bucket|
66
+ options[:bucket] = bucket
67
+ end
68
+ opts.on( '-g', '--group GROUP', 'Specify the default group of the repo dir' ) do |group|
69
+ options[:group] = group
70
+ end
71
+ opts.on( '-h', '--help', 'Display this screen' ) do
72
+ puts opts
73
+ exit
74
+ end
215
75
  end
216
76
 
217
- CLI.start
77
+ optparse.parse!
78
+
79
+ judo = Judo::Base.new(defaults.merge(options))
80
+
81
+ begin
82
+ case action
83
+ when "ips" then do_ips(judo)
84
+ when "volumes" then do_volumes(judo)
85
+ when "list" then do_list(judo, ARGV)
86
+ when "groups" then do_groups(judo)
87
+ when "info" then find_servers(judo, ARGV) { |s| do_info(judo, s) }
88
+ when "console" then find_servers(judo, ARGV) { |s| puts server.console_output }
89
+ when "commit" then find_groups(judo, ARGV) { |g| g.compile }
90
+ when "ssh" then find_servers(judo, ARGV) { |s| s.connect_ssh }
91
+ when "start" then find_servers(judo, ARGV) { |s| s.start }
92
+ when "restart" then find_servers(judo, ARGV) { |s| s.restart }
93
+ when "stop" then find_servers(judo, ARGV) { |s| s.stop }
94
+ when "create" then mk_servers(judo, ARGV) { |s| s.create }
95
+ when "launch" then mk_servers(judo, ARGV) { |s| s.create; s.start }
96
+ when "destroy" then
97
+ raise JudoError, "You must specify what servers to destroy" if ARGV.empty?
98
+ find_either(judo, ARGV) do |i|
99
+ i.destroy
100
+ end
101
+ else
102
+ raise JudoError, "No such action #{action}"
103
+ end
104
+ rescue JudoError => e
105
+ puts "Error: #{e.message}"
106
+ exit 1
107
+ end
data/lib/judo.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'yaml'
2
+ require 'socket'
3
+ require 'fileutils'
4
+
5
+ require 'rubygems'
6
+ require 'right_aws'
7
+ require 'json'
8
+ require 'pp'
9
+
10
+ class JudoError < RuntimeError ; end
11
+ class JudoInvalid < RuntimeError ; end
12
+
13
+ require File.dirname(__FILE__) + '/judo/base'
14
+ require File.dirname(__FILE__) + '/judo/config'
15
+ require File.dirname(__FILE__) + '/judo/group'
16
+ require File.dirname(__FILE__) + '/judo/server'
17
+ require File.dirname(__FILE__) + '/judo/setup'
18
+
data/lib/judo/base.rb ADDED
@@ -0,0 +1,199 @@
1
+ module Judo
2
+ class Base
3
+ attr_accessor :judo_dir, :repo, :group
4
+
5
+ def initialize(options)
6
+ @judo_dir = options[:judo_dir]
7
+ @repo = options[:repo]
8
+ @group = options[:group]
9
+ @bucket_name = options[:bucket]
10
+ @access_id = options[:access_id]
11
+ @access_secret = options[:access_secret]
12
+ end
13
+
14
+ def volumes
15
+ @volumes ||= ec2.describe_volumes.map do |v|
16
+ {
17
+ :id => v[:aws_id],
18
+ :size => v[:aws_size],
19
+ :status => v[:aws_status],
20
+ :device => v[:aws_device],
21
+ :instance_id => v[:aws_instance_id],
22
+ :attached_to => instance_id_to_judo(v[:aws_instance_id]),
23
+ :assigned_to => servers.detect { |s| s.volumes.invert[v[:aws_id]] }
24
+ }
25
+ end
26
+ end
27
+
28
+ def ips
29
+ @ips ||= ec2.describe_addresses.map do |ip|
30
+ {
31
+ :ip => ip[:public_ip],
32
+ :instance_id => ip[:instance_id],
33
+ :attached_to => instance_id_to_judo(ip[:instance_id]),
34
+ :assigned_to => ip_to_judo(ip[:public_ip])
35
+ }
36
+ end
37
+ end
38
+
39
+ def self.default_options(pwd, dir = find_judo_dir(pwd))
40
+ config = YAML.load File.read("#{dir}/config.yml")
41
+ repo_dir = config["repo"] || File.dirname(dir)
42
+ group_config = Dir["#{repo_dir}/*/config.json"].detect { |d| File.dirname(d) == pwd }
43
+ {
44
+ :judo_dir => dir,
45
+ :group => group_config ? File.basename(File.dirname(group_config)) : nil,
46
+ :repo => repo_dir,
47
+ :bucket => config["s3_bucket"],
48
+ :access_id => config["access_id"],
49
+ :access_secret => config["access_secret"]
50
+ }.delete_if { |key,value| value.nil? }
51
+ rescue Object => e
52
+ puts e.inspect
53
+ {}
54
+ end
55
+
56
+ def self.find_judo_dir(check)
57
+ if check == "/"
58
+ if File.exists?("#{ENV['HOME']}/.judo")
59
+ "#{ENV['HOME']}/.judo"
60
+ else
61
+ nil
62
+ end
63
+ else
64
+ File.exists?(check + "/.judo") ? check + "/.judo" : find_judo_dir(File.dirname(check))
65
+ end
66
+ end
67
+
68
+ def sdb
69
+ @sdb ||= Aws::SdbInterface.new(access_id, access_secret, :logger => Logger.new(nil))
70
+ end
71
+
72
+ def fetch_servers_state
73
+ s = {}
74
+ sdb.select("select * from #{Judo::Server.domain}")[:items].each do |group|
75
+ group.each do |key,val|
76
+ s[key] = val
77
+ end
78
+ end
79
+ s
80
+ end
81
+
82
+ def servers_state
83
+ @servers_state ||= fetch_servers_state
84
+ end
85
+
86
+ def servers
87
+ @servers ||= servers_state.map { |name,data| Judo::Server.new(self, name, data["group"].first) }
88
+ end
89
+
90
+ def new_server(name, group)
91
+ s = Judo::Server.new(self, name, group)
92
+ servers << s
93
+ s
94
+ end
95
+
96
+ def get_group(name)
97
+ group = groups.detect { |g| g.to_s == name }
98
+ group ||= Judo::Group.new(self, name, 0)
99
+ group
100
+ end
101
+
102
+ def task(msg, &block)
103
+ printf "---> %-24s ", "#{msg}..."
104
+ STDOUT.flush
105
+ start = Time.now
106
+ result = block.call
107
+ result = "done" unless result.is_a? String
108
+ finish = Time.now
109
+ time = sprintf("%0.1f", finish - start)
110
+ puts "#{result} (#{time}s)"
111
+ result
112
+ end
113
+
114
+ def groups
115
+ @groups ||= group_versions.map { |name,ver| Judo::Group.new(self, name, ver.first.to_i ) }
116
+ end
117
+
118
+ def reload_ec2_instances
119
+ @ec2_instance = nil
120
+ end
121
+
122
+ def ec2_instances
123
+ @ec2_instance ||= ec2.describe_instances
124
+ end
125
+
126
+ def ec2
127
+ @ec2 ||= Aws::Ec2.new(access_id, access_secret, :logger => Logger.new(nil))
128
+ end
129
+
130
+ def groups_config
131
+ @groups_config ||= sdb.get_attributes("judo_config", "groups")[:attributes]
132
+ end
133
+
134
+ def group_versions
135
+ @group_version ||= sdb.get_attributes("judo_config", "group_versions")[:attributes]
136
+ end
137
+
138
+ def ip_to_judo(ip)
139
+ servers.detect { |s| s.elastic_ip == ip }
140
+ end
141
+
142
+ def instance_id_to_judo(instance_id)
143
+ servers.detect { |s| s.instance_id and s.instance_id == instance_id }
144
+ end
145
+
146
+ def s3
147
+ @s3 ||= Aws::S3.new(access_id, access_secret, :logger => Logger.new(nil))
148
+ end
149
+
150
+ def bucket
151
+ @bucket ||= s3.bucket(bucket_name)
152
+ end
153
+
154
+ def s3_url(k)
155
+ Aws::S3Generator::Key.new(bucket, k).get
156
+ end
157
+
158
+ def s3_get(k)
159
+ bucket.get(k)
160
+ end
161
+
162
+ def s3_put(k, file)
163
+ bucket.put(k, file)
164
+ end
165
+
166
+ def repo
167
+ raise JudoError, "no repo dir specified" unless @repo
168
+ raise JudoError, "repo dir not found" unless File.exists?(@repo)
169
+ @repo
170
+ end
171
+
172
+ def access_id
173
+ @access_id || (raise JudoError, "no AWS Access ID specified")
174
+ end
175
+
176
+ def access_secret
177
+ @access_secret || (raise JudoError, "no AWS Secret Key specified")
178
+ end
179
+
180
+ def bucket_name
181
+ @bucket_name || (raise JudoError, "no S3 bucket name specified")
182
+ end
183
+
184
+ def db_version
185
+ 1
186
+ end
187
+
188
+ def get_db_version
189
+ version = @sdb.get_attributes("judo_config", "judo")[:attributes]["dbversion"]
190
+ version and version.first.to_i or db_version
191
+ end
192
+
193
+ def check_version
194
+ ## FIXME - call this somewhere
195
+ raise JduoError, "judo db is newer than the current gem - upgrade judo and try again" if get_db_version > db_version
196
+ end
197
+
198
+ end
199
+ end