rudy 0.6.8 → 0.7.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.
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