judo 0.0.9 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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