rudy 0.6.8 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/CHANGES.txt +15 -2
  2. data/README.rdoc +30 -23
  3. data/Rakefile +5 -5
  4. data/Rudyfile +87 -66
  5. data/bin/rudy +120 -167
  6. data/bin/rudy-ec2 +17 -13
  7. data/bin/rudy-s3 +6 -4
  8. data/bin/rudy-sdb +5 -4
  9. data/lib/annoy.rb +1 -1
  10. data/lib/console.rb +1 -3
  11. data/lib/rudy.rb +11 -25
  12. data/lib/rudy/aws/ec2/instance.rb +1 -1
  13. data/lib/rudy/aws/ec2/volume.rb +2 -2
  14. data/lib/rudy/aws/sdb/error.rb +2 -1
  15. data/lib/rudy/cli.rb +10 -1
  16. data/lib/rudy/cli/aws/ec2/addresses.rb +1 -1
  17. data/lib/rudy/cli/aws/ec2/images.rb +3 -1
  18. data/lib/rudy/cli/aws/ec2/instances.rb +2 -2
  19. data/lib/rudy/cli/candy.rb +11 -0
  20. data/lib/rudy/cli/config.rb +25 -44
  21. data/lib/rudy/cli/machines.rb +30 -10
  22. data/lib/rudy/cli/routines.rb +67 -19
  23. data/lib/rudy/config.rb +30 -13
  24. data/lib/rudy/config/objects.rb +135 -10
  25. data/lib/rudy/disks.rb +8 -52
  26. data/lib/rudy/global.rb +9 -5
  27. data/lib/rudy/guidelines.rb +18 -0
  28. data/lib/rudy/huxtable.rb +29 -19
  29. data/lib/rudy/machines.rb +10 -7
  30. data/lib/rudy/mixins/hash.rb +25 -0
  31. data/lib/rudy/routines.rb +160 -10
  32. data/lib/rudy/routines/helper.rb +50 -0
  33. data/lib/rudy/routines/helpers/diskhelper.rb +44 -18
  34. data/lib/rudy/routines/helpers/scmhelper.rb +39 -0
  35. data/lib/rudy/routines/helpers/scripthelper.rb +86 -35
  36. data/lib/rudy/routines/helpers/userhelper.rb +37 -0
  37. data/lib/rudy/routines/passthrough.rb +36 -0
  38. data/lib/rudy/routines/release.rb +38 -22
  39. data/lib/rudy/routines/shutdown.rb +20 -49
  40. data/lib/rudy/routines/startup.rb +20 -47
  41. data/lib/rudy/scm.rb +75 -0
  42. data/lib/rudy/scm/git.rb +215 -0
  43. data/lib/rudy/scm/svn.rb +7 -6
  44. data/lib/rudy/utils.rb +12 -30
  45. data/lib/storable.rb +4 -1
  46. data/lib/sysinfo.rb +10 -0
  47. data/rudy.gemspec +21 -9
  48. data/test/01_mixins/10_hash_test.rb +25 -0
  49. data/test/{05_config → 10_config}/00_setup_test.rb +1 -1
  50. data/test/{05_config → 10_config}/30_machines_test.rb +1 -1
  51. data/test/15_scm/00_setup_test.rb +20 -0
  52. data/test/15_scm/20_git_test.rb +61 -0
  53. data/test/helper.rb +1 -1
  54. data/vendor/highline-1.5.1/Rakefile +3 -3
  55. metadata +41 -12
  56. data/bin/ird +0 -175
data/lib/rudy/global.rb CHANGED
@@ -42,9 +42,9 @@ module Rudy
42
42
  postprocess
43
43
  # These attributes MUST have values.
44
44
  @verbose ||= 0
45
- @nocolor ||= false
45
+ @nocolor = true unless @nocolor == "false" || @nocolor.is_a?(FalseClass)
46
46
  @quiet ||= false
47
- @format ||= 'string' # as in, to_s
47
+ @format ||= :string # as in, to_s
48
48
  @print_header = true if @print_header == nil
49
49
  @yes = false if @yes.nil?
50
50
  end
@@ -52,16 +52,18 @@ module Rudy
52
52
  def apply_config(config)
53
53
  return unless config.is_a?(Rudy::Config)
54
54
  if config.defaults?
55
+ # Apply the "color" default before "nocolor" so nocolor has presedence
56
+ @nocolor = !config.defaults.color unless config.defaults.color.nil?
55
57
  %w[region zone environment role position user nocolor quiet].each do |name|
56
58
  val = config.defaults.send(name)
57
- self.send("#{name}=", val) if val
59
+ self.send("#{name}=", val) unless val.nil?
58
60
  end
59
61
  end
60
62
 
61
63
  if config.accounts? && config.accounts.aws
62
64
  %w[accesskey secretkey accountnum cert privatekey].each do |name|
63
65
  val = config.accounts.aws.send(name)
64
- self.send("#{name}=", val) if val
66
+ self.send("#{name}=", val) unless val.nil?
65
67
  end
66
68
  end
67
69
 
@@ -88,10 +90,12 @@ module Rudy
88
90
  def postprocess
89
91
  apply_environment_variables
90
92
  apply_system_defaults
91
-
93
+
94
+ @nocolor = !@color unless @color.nil?
92
95
  @cert &&= File.expand_path(@cert)
93
96
  @privatekey &&= File.expand_path(@privatekey)
94
97
  @position &&= @position.to_s.rjust(2, '0')
98
+ @format &&= @format.to_sym rescue nil
95
99
 
96
100
  String.disable_color if @nocolor
97
101
  Rudy.enable_quiet if @quiet
@@ -0,0 +1,18 @@
1
+
2
+
3
+ module Rudy
4
+ module Guidelines #:nodoc:all
5
+ extend self
6
+ AFE = "Always fail early" # [ed: the A's are a work in progress]
7
+ ABA = "Always be accurate"
8
+ CBC = "Consistency before cuteness"
9
+ UNO = "Ugly's not okay"
10
+ def inspect
11
+ all = Guidelines.constants
12
+ g = all.collect { |c| '%s="%s"' % [c, const_get(c)] }
13
+ %q{#<Rudy::Guidelines:%s %s>} % [self.object_id, g.join(' ')]
14
+ end
15
+ end
16
+ end
17
+
18
+ puts Rudy::Guidelines.inspect if __FILE__ == $0
data/lib/rudy/huxtable.rb CHANGED
@@ -22,6 +22,7 @@ module Rudy
22
22
  # TODO: investigate @@debug bug. When this is true, Caesars.debug? returns true
23
23
  # too. It's possible this is intentional but probably not.
24
24
  @@debug = false
25
+ @@abort = false
25
26
 
26
27
  @@config = Rudy::Config.new
27
28
  @@global = Rudy::Global.new
@@ -42,8 +43,6 @@ module Rudy
42
43
  @@global.apply_config(@@config)
43
44
  end
44
45
 
45
- update_config
46
-
47
46
  def self.update_global(ghash={})
48
47
  @@global.update(ghash)
49
48
  end
@@ -72,8 +71,8 @@ module Rudy
72
71
  def self.change_environment(v); @@global.environment = v; end
73
72
  def self.change_position(v); @@global.position = v; end
74
73
 
75
- def debug?; @@debug == true; end
76
-
74
+ def debug?; Rudy::Huxtable.debug?; end
75
+ def Huxtable.debug?; @@debug == true; end
77
76
  def check_keys
78
77
  raise "No EC2 .pem keys provided" unless has_pem_keys?
79
78
  raise "No SSH key provided for #{current_user}!" unless has_keypair?
@@ -156,10 +155,6 @@ module Rudy
156
155
  def current_user_keypairpath
157
156
  user_keypairpath(current_user)
158
157
  end
159
- def current_machine_hostname(group=nil)
160
- group ||= machine_group
161
- find_machine(group)[:dns_name]
162
- end
163
158
 
164
159
  def current_machine_group
165
160
  [@@global.environment, @@global.role].join(Rudy::DELIM)
@@ -173,16 +168,21 @@ module Rudy
173
168
  fetch_machine_param(:positions) || 1
174
169
  end
175
170
 
171
+ def current_machine_hostname
172
+ # NOTE: There is an issue with Caesars that a keyword that has been
173
+ # defined as forced_array (or forced_hash, etc...) is like that for
174
+ # all subclasses of Caesars. There is a conflict between "hostname"
175
+ # in the machines config and routines config. The routines config
176
+ # parses hostname with forced_array because it's a shell command
177
+ # in Rye::Cmd. Machines config expects just a symbol. The issue
178
+ # is with Caesars so this is a workaround to return a symbol.
179
+ hn = fetch_machine_param(:hostname) || :rudy
180
+ hn = hn.flatten.compact.first if hn.is_a?(Array)
181
+ hn
182
+ end
183
+
176
184
  def current_machine_image
177
185
  fetch_machine_param(:ami)
178
- #zon, env, rol = @@global.zone, @@global.environment, @@global.role
179
- #ami = @@config.machines.find_deferred([zon, env, rol]) || {}
180
- #ami.merge!(@@config.machines.find_deferred(env, rol, :ami))
181
- #ami.merge!(@@config.machines.find_deferred(rol, :ami))
182
- ## I commented this out while cleaning (start of 0.6 branch) . It
183
- ## seems like a bad idea. I don't want Huxtables throwing exceptions.
184
- ##raise Rudy::NoMachineImage, current_machine_group unless ami
185
- #ami
186
186
  end
187
187
 
188
188
  def current_machine_size
@@ -268,22 +268,29 @@ module Rudy
268
268
  # :size: 1
269
269
  #
270
270
  def fetch_routine_config(action)
271
+ raise "No action specified" unless action
271
272
  raise NoConfig unless @@config
272
273
  raise NoRoutinesConfig unless @@config.routines
273
274
  raise NoGlobal unless @@global
274
275
 
275
276
  zon, env, rol = @@global.zone, @@global.environment, @@global.role
276
277
 
277
- disk_defs = fetch_machine_param(:disks)
278
+ disk_defs = fetch_machine_param(:disks) || {}
278
279
 
279
- routine = @@config.routines.find(@@global.environment, @@global.role, action)
280
+ # We want to find only one routines config with the name +action+.
281
+ # This is unlike the routines config where it's okay to merge via
282
+ # precedence.
283
+ routine = @@config.routines.find_deferred(@@global.environment, @@global.role, action)
284
+ routine ||= @@config.routines.find_deferred([@@global.environment, @@global.role], action)
285
+ routine ||= @@config.routines.find_deferred(@@global.role, action)
280
286
  return nil unless routine
287
+ return routine unless routine.has_key?(:disks)
281
288
 
282
289
  routine.disks.each_pair do |raction,disks|
283
290
 
284
291
  disks.each_pair do |path, props|
285
292
  unless disk_defs.has_key?(path)
286
- @logger.puts "#{path} is not defined. Check your #{action} routines config.".color(:red)
293
+ @@logger.puts "#{path} is not defined. Check your #{action} machines config.".color(:red)
287
294
  routine.disks[raction].delete(path)
288
295
  next
289
296
  end
@@ -298,6 +305,7 @@ module Rudy
298
305
 
299
306
 
300
307
  def fetch_machine_param(parameter)
308
+ raise "No parameter specified" unless parameter
301
309
  raise NoConfig unless @@config
302
310
  raise NoMachinesConfig unless @@config.machines
303
311
  raise NoGlobal unless @@global
@@ -331,6 +339,7 @@ module Rudy
331
339
  # :region, :zone, :environment, :role, :position
332
340
  def fetch_script_config
333
341
  sconf = fetch_machine_param :config
342
+ sconf ||= {}
334
343
  extras = {
335
344
  :region => @@global.region,
336
345
  :zone => @@global.zone,
@@ -345,3 +354,4 @@ module Rudy
345
354
 
346
355
  end
347
356
  end
357
+
data/lib/rudy/machines.rb CHANGED
@@ -91,8 +91,9 @@ module Rudy
91
91
  @state = @instance.state
92
92
  save
93
93
  elsif @instance.nil?
94
- @dns_public = @dns_private = nil
95
- @state = 'unknown'
94
+ @awsid = @dns_public = @dns_private = nil
95
+ @state = 'rogue'
96
+ # Don't save it b/c it's possible the EC2 server is just down.
96
97
  end
97
98
  end
98
99
 
@@ -120,14 +121,14 @@ module Rudy
120
121
  @awsid = inst.awsid
121
122
  @created = @starts = Time.now
122
123
  @state = inst.state
123
- # Assign IP address to only the first instance
124
+ # Assign IP address only if we have one for that position
124
125
  if current_machine_address(@position)
125
126
  address = current_machine_address(@position)
126
127
  puts "Associating #{address} to #{inst.awsid}"
127
128
  begin
128
129
  @radd.associate(address, inst.awsid)
129
130
  rescue => ex
130
- STDERR.puts "Error while associating address."
131
+ STDERR.puts "Error while associating address (#{ex.class.to_s})"
131
132
  Rudy::Utils.bug()
132
133
  end
133
134
  end
@@ -201,7 +202,9 @@ module Rudy
201
202
  machines = []
202
203
  current_machine_count.times do |i|
203
204
  machine = Rudy::Machine.new
204
- puts "Starting %s" % machine.name
205
+
206
+ #puts "Starting %s" % machine.name
207
+
205
208
  machine.start
206
209
  machines << machine
207
210
  end
@@ -215,13 +218,13 @@ module Rudy
215
218
  raise MachineGroupNotRunning, current_machine_group unless running?
216
219
  list.each { |m| each_mach.call(m); } if each_mach
217
220
  list do |mach|
218
- puts "Destroying #{mach.name}"
221
+ #puts "Destroying #{mach.name}"
219
222
  mach.destroy
220
223
  end
221
224
  end
222
225
 
223
226
  def list(more=[], less=[], &each_mach)
224
- machines = list_as_hash(&each_mach)
227
+ machines = list_as_hash(more, less, &each_mach)
225
228
  machines &&= machines.values
226
229
  machines
227
230
  end
@@ -0,0 +1,25 @@
1
+
2
+ class Hash
3
+
4
+ # A depth-first look to find the deepest point in the Hash.
5
+ # The top level Hash is counted in the total so the final
6
+ # number is the depth of its children + 1. An example:
7
+ #
8
+ # ahash = { :level1 => { :level2 => {} } }
9
+ # ahash.deepest_point # => 3
10
+ #
11
+ def deepest_point(h=self, steps=0)
12
+ if h.is_a?(Hash)
13
+ steps += 1
14
+ h.each_pair do |n,possible_h|
15
+ ret = deepest_point(possible_h, steps)
16
+ steps = ret if steps < ret
17
+ end
18
+ else
19
+ return 0
20
+ end
21
+ steps
22
+ end
23
+
24
+ end
25
+
data/lib/rudy/routines.rb CHANGED
@@ -2,10 +2,14 @@
2
2
 
3
3
  module Rudy
4
4
  module Routines
5
+ class NoRoutine < Rudy::Error
6
+ def message; "No routine configuration for #{@obj}"; end
7
+ end
8
+
5
9
  class Base
6
10
  include Rudy::Huxtable
7
11
 
8
- def initialize
12
+ def initialize(*args)
9
13
  a, s, r = @@global.accesskey, @@global.secretkey, @@global.region
10
14
  @sdb = Rudy::AWS::SDB.new(a, s, r)
11
15
  @rinst = Rudy::AWS::EC2::Instances.new(a, s, r)
@@ -13,35 +17,181 @@ module Rudy
13
17
  @rkey = Rudy::AWS::EC2::KeyPairs.new(a, s, r)
14
18
  @rvol = Rudy::AWS::EC2::Volumes.new(a, s, r)
15
19
  @rsnap = Rudy::AWS::EC2::Snapshots.new(a, s, r)
16
- init
20
+ init(*args)
17
21
  end
18
22
 
19
23
  def init
20
24
  end
21
25
 
22
-
23
26
  def execute
24
27
  raise "Override execute method"
25
28
  end
26
29
 
30
+ def raise_early_exceptions
31
+ raise "Must override raise_early_exceptions"
32
+ end
33
+
34
+ # * +machine_action+ a method on Rudy::Machines, one of: create, destroy, list
35
+ # * +skipcheck+ Skip checking if machine is up and SSH is available (default: false)
36
+ # * +routine_action+ is an optional block which represents the action
37
+ # for a specific routine. For example, a startup routine will start
38
+ # an EC2 instance. Arguments: instances of Rudy::Machine and Rye::Box.
39
+ def generic_machine_runner(machine_action, skipcheck=false, &routine_action)
40
+ rmach = Rudy::Machines.new
41
+ raise "No routine supplied" unless @routine
42
+ raise "No machine action supplied" unless machine_action
43
+ unless rmach.respond_to?(machine_action)
44
+ raise "Unknown machine action #{machine_action}"
45
+ end
46
+
47
+ lbox = Rye::Box.new('localhost')
48
+ sconf = fetch_script_config
49
+
50
+
51
+ if Rudy::Routines::ScriptHelper.before_local?(@routine) # before_local
52
+ # Runs "before_local" scripts of routines config.
53
+ # NOTE: Does not run "before" scripts b/c there are no remote machines
54
+ puts task_separator("LOCAL SHELL")
55
+ Rudy::Routines::ScriptHelper.before_local(@routine, sconf, lbox)
56
+ end
57
+
58
+ # Execute the action (create, list, destroy) & apply the block to each
59
+ rmach.send(machine_action) do |machine|
60
+ puts machine_separator(machine.name, machine.awsid)
61
+
62
+ unless skipcheck
63
+ msg = preliminary_separator("Checking if instance is running...")
64
+ Rudy::Utils.waiter(3, 120, STDOUT, msg, 0) {
65
+ inst = machine.get_instance
66
+ inst && inst.running?
67
+ }
68
+
69
+ # Add instance info to machine and save it. This is really important
70
+ # for the initial startup so the metadata is updated right away. But
71
+ # it's also important to call here because if a routine was executed
72
+ # and an unexpected exception occurrs before this update is executed
73
+ # the machine metadata won't contain the DNS information. Calling it
74
+ # here ensure that the metadata is always up-to-date.
75
+ machine.update
76
+
77
+ msg = preliminary_separator("Waiting for SSH daemon...")
78
+ Rudy::Utils.waiter(2, 60, STDOUT, msg, 0) {
79
+ Rudy::Utils.service_available?(machine.dns_public, 22)
80
+ }
81
+ end
82
+
83
+
84
+ # TODO: trap rbox errors. We could get an authentication error.
85
+ opts = { :keys => root_keypairpath, :user => 'root', :info => @@global.verbose > 0 }
86
+ begin
87
+ rbox = Rye::Box.new(machine.dns_public, opts)
88
+ rbox.connect
89
+ rescue Rye::NoHost => ex
90
+
91
+ exit 65
92
+ end
93
+
94
+ # Set the hostname if specified in the machines config.
95
+ # :rudy -> change to Rudy's machine name
96
+ # :default -> leave the hostname as it is
97
+ # Anything else other than nil -> change to that value
98
+ # NOTE: This will set hostname every time a routine is
99
+ # run so we may want to make this an explicit action.
100
+ hn = current_machine_hostname || :rudy
101
+ if hn != :default
102
+ hn = machine.name if hn == :rudy
103
+ print preliminary_separator("Setting hostame to #{hn}... ")
104
+ rbox.hostname(hn)
105
+ puts "done"
106
+ end
107
+
108
+ if Rudy::Routines::UserHelper.adduser?(@routine) # adduser
109
+ puts task_separator("ADD USER")
110
+ Rudy::Routines::UserHelper.adduser(@routine, machine, rbox)
111
+ end
112
+
113
+ if Rudy::Routines::UserHelper.authorize?(@routine) # authorize
114
+ puts task_separator("AUTHORIZE USER")
115
+ Rudy::Routines::UserHelper.authorize(@routine, machine, rbox)
116
+ end
117
+
118
+ if Rudy::Routines::ScriptHelper.before?(@routine) # before
119
+ puts task_separator("REMOTE SHELL")
120
+ Rudy::Routines::ScriptHelper.before(@routine, sconf, machine, rbox)
121
+ end
122
+
123
+ if Rudy::Routines::DiskHelper.disks?(@routine) # disk
124
+ puts task_separator("DISKS")
125
+ if rbox.ostype == "sunos"
126
+ puts "Sorry, Solaris is not supported yet!"
127
+ else
128
+ Rudy::Routines::DiskHelper.execute(@routine, machine, rbox)
129
+ end
130
+ end
131
+
132
+ # Startup, shutdown, release, deploy, etc...
133
+ routine_action.call(machine, rbox) if routine_action
134
+
135
+ if Rudy::Routines::ScriptHelper.after?(@routine) # after
136
+ puts task_separator("REMOTE SHELL")
137
+ # Runs "after" scripts of routines config
138
+ Rudy::Routines::ScriptHelper.after(@routine, sconf, machine, rbox)
139
+ end
140
+
141
+ if Rudy::Routines::DiskHelper.disks?(@routine)
142
+ # TODO: Print only the requested disks
143
+ puts task_separator("INFO")
144
+ puts "Filesystem on #{machine.name}:"
145
+ puts " " << rbox.df(:h).join("#{$/} ")
146
+ end
147
+
148
+ rbox.disconnect
149
+ end
150
+
151
+ if Rudy::Routines::ScriptHelper.after_local?(@routine) # after_local
152
+ puts task_separator("LOCAL SHELL")
153
+ # Runs "after_local" scripts of routines config
154
+ Rudy::Routines::ScriptHelper.after_local(@routine, sconf, lbox)
155
+ end
156
+
157
+ end
158
+
159
+ def preliminary_separator(msg)
160
+ # TODO: Count number messages printed 1/3. ie:
161
+ # m-us-east-1b-stage-app-01
162
+ # (1/3) Checking if instance is running... done
163
+ # (2/3) Waiting for SSH daemon... done
164
+ # (3/3) Setting hostame to m-us-east-1b-stage-app-01... done
165
+ (" -> #{msg}")
166
+ end
167
+
27
168
  def task_separator(title)
28
- dashes = 52 - title.size #
169
+ dashes = 59 - title.size
29
170
  dashes = 0 if dashes < 1
30
- ("%s--- %s %s" % [$/, title, '-'*dashes]).bright
171
+ ("%s--- %s %s" % [$/, title, '-'*dashes])
31
172
  end
32
173
 
33
- def machine_separator(title)
34
- dashes = 52 - title.size #
174
+ def machine_separator(name, awsid)
175
+ dashes = 80 - name.size #
35
176
  dashes = 0 if dashes < 1
36
- ("%s--- %s %s" % [$/, title.bright, '-'*dashes]).bright.color(:blue)
177
+ ('%s %-63s awsid: %s ' % [$/, name, awsid]).att(:reverse)
37
178
  end
38
-
179
+
180
+ def routine_separator(name)
181
+ # Not used (for now)
182
+ name = name.to_s
183
+ dashes = 59 - name.size #
184
+ dashes = 0 if dashes < 1
185
+ #puts '%-40s' % [name.bright]
186
+ end
187
+
188
+
39
189
  end
40
190
 
41
191
  end
42
192
  end
43
193
 
44
- Rudy::Utils.require_glob(RUDY_LIB, 'rudy', 'routines', 'helpers', '*.rb')
45
194
  Rudy::Utils.require_glob(RUDY_LIB, 'rudy', 'routines', '*.rb')
195
+ Rudy::Utils.require_glob(RUDY_LIB, 'rudy', 'routines', 'helpers', '*.rb')
46
196
 
47
197