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
@@ -0,0 +1,50 @@
1
+
2
+
3
+ module Rudy
4
+ module Routines
5
+ module HelperBase
6
+ include Rudy::Huxtable
7
+
8
+ def execute_rbox_command(ret=nil, &command)
9
+ begin
10
+ ret = command.call
11
+ puts ' ' << ret.stdout.join("#{$/} ") if !ret.stdout.empty?
12
+ print_response(ret)
13
+ rescue Rye::CommandError => ex
14
+ print_response(ex)
15
+ exit 12 unless keep_going?
16
+ rescue Rye::CommandNotFound => ex
17
+ STDERR.puts " CommandNotFound: #{ex.message}".color(:red)
18
+ STDERR.puts ex.backtrace if Rudy.debug?
19
+ exit 12 unless keep_going?
20
+ end
21
+
22
+ ret
23
+ end
24
+
25
+ def keep_going?
26
+ Annoy.pose_question(" Keep going?\a ", /yes|y|ya|sure|you bet!/i, STDERR)
27
+ end
28
+
29
+ # Returns a formatted string for printing command info
30
+ def command_separator(cmd, user)
31
+ cmd ||= ""
32
+ cmd, user = cmd.to_s, user.to_s
33
+ prompt = user == "root" ? "#" : "$"
34
+ ("%s%s%s %s" % [$/, user, prompt, cmd.bright])
35
+ end
36
+
37
+ private
38
+ def print_response(rap)
39
+ colour = rap.exit_code != 0 ? :red : :normal
40
+ [:stderr].each do |sumpin|
41
+ next if rap.send(sumpin).empty?
42
+ STDERR.puts (" #{sumpin.upcase.to_s} " << '-'*38).color(colour).bright
43
+ STDERR.puts " " << rap.send(sumpin).join("#{$/} ").color(colour)
44
+ end
45
+ STDERR.puts " Exit code: #{rap.exit_code}".color(colour) if rap.exit_code != 0
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -2,8 +2,19 @@
2
2
 
3
3
  module Rudy; module Routines;
4
4
  module DiskHelper
5
+ include Rudy::Routines::HelperBase # TODO: use execute_rbox_command
5
6
  extend self
6
7
 
8
+ def disks?(routine)
9
+ (routine.is_a?(Caesars::Hash) && routine.disks &&
10
+ routine.disks.is_a?(Caesars::Hash) && !routine.disks.empty?)
11
+ end
12
+
13
+ def paths(routine)
14
+ return nil unless disks?(routine)
15
+ routine.disks.values.collect { |d| d.keys }.flatten
16
+ end
17
+
7
18
  def execute(routine, machine, rbox)
8
19
  return unless routine
9
20
  raise "Not a Rudy::Machine" unless machine.is_a?(Rudy::Machine)
@@ -12,13 +23,20 @@ module Rudy; module Routines;
12
23
  @machine = machine
13
24
  @rbox = rbox
14
25
 
15
- (routine.disks || {}).each_pair do |action, disks|
26
+ # We need to add mkfs since it's not enabled by default.
27
+ # We add it only to this instance we're using.
28
+ def @rbox.mkfs(*args); cmd('mkfs', args); end
29
+
30
+ return unless disks?(routine)
31
+
32
+ routine.disks.each_pair do |action, disks|
16
33
  unless DiskHelper.respond_to?(action)
17
34
  STDERR.puts %Q(DiskHelper: unknown action "#{action}")
18
35
  next
19
36
  end
20
37
  send(action, disks) # create, copy, destroy, ...
21
38
  end
39
+
22
40
  end
23
41
 
24
42
  def create(disks)
@@ -26,18 +44,23 @@ module Rudy; module Routines;
26
44
 
27
45
  disks.each_pair do |path, props|
28
46
  disk = Rudy::Disk.new(path, props[:size], props[:device], @machine.position)
29
- #disk = rdisk.get(disk.name)
47
+ olddisk = rdisk.get(disk.name)
48
+ if olddisk && olddisk.exists?
49
+ puts "Disk exists: #{olddisk.name}".color(:red)
50
+ return
51
+ end
52
+
30
53
  puts "Creating #{disk.name} "
31
54
 
32
- print "Creating volume... "
55
+ msg = "Creating volume... "
33
56
  disk.create
34
- Rudy::Utils.waiter(2, 60, STDOUT, "done", nil) {
57
+ Rudy::Utils.waiter(2, 60, STDOUT, msg) {
35
58
  disk.available?
36
59
  }
37
60
 
38
- print "Attaching #{disk.awsid} to #{@machine.awsid}... "
61
+ msg = "Attaching #{disk.awsid} to #{@machine.awsid}... "
39
62
  disk.attach(@machine.awsid)
40
- Rudy::Utils.waiter(2, 60, STDOUT, "done", nil) {
63
+ Rudy::Utils.waiter(2, 10, STDOUT, msg) {
41
64
  disk.attached?
42
65
  }
43
66
 
@@ -46,15 +69,19 @@ module Rudy; module Routines;
46
69
  # "No such file or directory while trying to determine filesystem size"
47
70
  sleep 2
48
71
 
72
+ # TODO: Cleanup. See ScriptHelper
49
73
  begin
50
74
  print "Creating ext3 filesystem for #{disk.device}... "
51
- @rbox.mkfs(:t, "ext3", :F, disk.device)
52
- @rbox.mkdir(:p, disk.path)
53
- puts "done"
75
+ execute_rbox_command {
76
+
77
+ @rbox.mkfs(:t, "ext3", :F, disk.device)
78
+ @rbox.mkdir(:p, disk.path)
79
+ puts "done"
54
80
 
55
- print "Mounting at #{disk.path}... "
81
+ print "Mounting at #{disk.path}... "
56
82
 
57
- @rbox.mount(:t, 'ext3', disk.device, disk.path)
83
+ @rbox.mount(:t, 'ext3', disk.device, disk.path)
84
+ }
58
85
  disk.mounted = true
59
86
  disk.save
60
87
  rescue Net::SSH::AuthenticationFailed, Net::SSH::HostKeyMismatch => ex
@@ -85,24 +112,23 @@ module Rudy; module Routines;
85
112
  puts "Destroying #{disk.name}"
86
113
 
87
114
  if disk.mounted?
88
- print "Unmounting #{disk.path}... "
89
- @rbox.umount(disk.path)
115
+ print "Unmounting #{disk.path}..."
116
+ execute_rbox_command { @rbox.umount(disk.path) }
117
+ puts " done"
90
118
  sleep 0.5
91
- puts "done"
92
119
  end
93
120
 
94
121
  if disk.attached?
95
- print "Detaching #{disk.awsid}... "
122
+ msg = "Detaching #{disk.awsid}..."
96
123
  disk.detach
97
- Rudy::Utils.waiter(2, 60, STDOUT, 'done', nil) {
124
+ Rudy::Utils.waiter(2, 60, STDOUT, msg) {
98
125
  disk.available?
99
126
  }
100
127
  sleep 0.5
101
128
  end
102
129
 
103
- print "Destroying metadata... "
130
+ puts "Destroying metadata... "
104
131
  disk.destroy
105
- puts "done"
106
132
 
107
133
  end
108
134
  end
@@ -0,0 +1,39 @@
1
+
2
+ module Rudy; module Routines;
3
+ module SCMHelper
4
+ include Rudy::Routines::HelperBase
5
+ extend self
6
+
7
+ # Does the routine config contain SCM routines?
8
+ # Raises Rudy::Error if there is malformed configuration.
9
+ def scm?(routine)
10
+ scmnames = SUPPORTED_SCM_NAMES & routine.keys # Find intersections.
11
+ return false if scmnames.empty? # Nothing to do.
12
+ scmnames.each do |scm|
13
+ routine[scm].values.each do |p| # Each SCM should have a
14
+ raise "Bad #{scm} config" if !p.kind_of?(Hash) # Hash config. Otherwise
15
+ end # it's misconfigured.
16
+ end
17
+ true
18
+ end
19
+
20
+ def create_scm_objects(routine)
21
+ return nil unless routine
22
+ scmnames = SUPPORTED_SCM_NAMES & routine.keys
23
+ vlist = []
24
+ # Look for scm config in the routine by checking all known scm types.
25
+ # For each one we'll create an instance of the appropriate SCM class.
26
+ scmnames.each do |scm|
27
+ routine[scm].each_pair do |user,params|
28
+ klass = eval "Rudy::SCM::#{scm.to_s.upcase}"
29
+ params[:user] = user
30
+ scm = klass.new(params)
31
+ scm.raise_early_exceptions # Raises exceptions for obvious problems.
32
+ vlist << scm
33
+ end
34
+ end
35
+ vlist
36
+ end
37
+
38
+ end
39
+ end; end
@@ -1,34 +1,69 @@
1
1
  require 'tempfile'
2
2
 
3
-
4
3
  module Rudy; module Routines;
5
-
4
+ #--
5
+ # TODO: Rename to ShellHelper
6
+ #++
6
7
  module ScriptHelper
8
+ include Rudy::Routines::HelperBase # TODO: use execute_rbox_command
7
9
  extend self
8
-
10
+
9
11
  @@script_types = [:after, :before, :after_local, :before_local]
10
12
  @@script_config_file = "rudy-config.yml"
11
13
 
12
14
  def before_local(routine, sconf, rbox)
15
+
16
+ # before_local generally doesn't take a user name like the remote
17
+ # before block so we add it here (unless the user did specify it)
18
+ routine[:before_local] = {
19
+ rbox.user.to_sym => routine.delete(:before_local)
20
+ } unless routine.has_key?(rbox.user.to_sym) # use routine[timing].deepest_point ?
13
21
  execute_command(:before_local, routine, sconf, 'localhost', rbox)
14
- end
22
+ end
23
+ def before_local?(routine); execute_command?(:before_local, routine); end
24
+
15
25
  def after_local(routine, sconf, rbox)
26
+ routine[:after_local] = { # See before_local note
27
+ rbox.user.to_sym => routine.delete(:after_local)
28
+ } unless routine.has_key?(rbox.user.to_sym)
16
29
  execute_command(:after_local, routine, sconf, 'localhost', rbox)
17
30
  end
18
-
31
+ def after_local?(routine); execute_command?(:after_local, routine); end
32
+
33
+
19
34
  def before(routine, sconf, machine, rbox)
20
35
  raise "ScriptHelper: Not a Rudy::Machine" unless machine.is_a?(Rudy::Machine)
21
36
  execute_command(:before, routine, sconf, machine.name, rbox)
22
37
  end
38
+ def before?(routine); execute_command?(:before, routine); end
39
+
23
40
  def after(routine, sconf, machine, rbox)
24
41
  raise "ScriptHelper: Not a Rudy::Machine" unless machine.is_a?(Rudy::Machine)
25
42
  execute_command(:after, routine, sconf, machine.name, rbox)
26
43
  end
44
+ def after?(routine); execute_command?(:after, routine); end
27
45
 
28
46
 
29
47
  private
30
48
 
31
- # * +timing+ is one of: after, before
49
+ # Does the routine have the requested script type?
50
+ # * +timing+ is one of: after, before, after_local, before_local
51
+ # * +routine+ a single routine hash (startup, shutdown, etc...)
52
+ # Prints notice to STDERR if there's an empty conf hash
53
+ def execute_command?(timing, routine)
54
+ hasconf = (routine.is_a?(Caesars::Hash) && routine.has_key?(timing))
55
+ return false unless hasconf
56
+ routine[timing].each_pair do |user,conf|
57
+ if conf.empty?
58
+ STDERR.puts "Empty #{timing} config for #{user}"
59
+ else
60
+ return true
61
+ end
62
+ end
63
+ false
64
+ end
65
+
66
+ # * +timing+ is one of: after, before, after_local, before_local
32
67
  # * +routine+ a single routine hash (startup, shutdown, etc...)
33
68
  # * +sconf+ is a config hash from machines config (ignored if nil)
34
69
  # * +hostname+ machine hostname that we're working on
@@ -47,48 +82,64 @@ module Rudy; module Routines;
47
82
  # add the method on for the instance of rbox we are using.
48
83
  def rbox.rm(*args); cmd('rm', args); end
49
84
 
50
-
51
- if routine.is_a?(Caesars::Hash) && routine.has_key?(timing)
52
- puts "Connecting to #{hostname}"
53
- begin
54
- rbox.connect
55
- rescue Net::SSH::AuthenticationFailed, Net::SSH::HostKeyMismatch => ex
56
- STDERR.puts "Error connecting: #{ex.message}".color(:red)
57
- STDERR.puts "Skipping scripts".color(:red)
58
- end
85
+ if execute_command?(timing, routine) # i.e. before_local?
59
86
 
60
87
  original_user = rbox.user
61
- scripts = [routine[timing]].flatten
62
- scripts.each do |script|
88
+ users = routine[timing] || {}
89
+ users.each_pair do |user, commands|
63
90
 
64
91
  begin
65
- user, command, *args = script.to_a.flatten.compact
66
92
  rbox.switch_user user # does nothing if it's the same user
67
- puts "Creating #{@@script_config_file}"
68
- rbox.safe = false
69
- puts rbox.echo("'#{sconf.to_hash.to_yaml}' > #{@@script_config_file}")
70
- rbox.safe = true
71
- rbox.chmod(600, @@script_config_file)
72
- puts %Q{Running (as #{user}): #{rbox.preview_command(command, args)}}
93
+ rbox.connect(false) # does nothing if already connected
94
+ rescue Net::SSH::AuthenticationFailed, Net::SSH::HostKeyMismatch => ex
95
+ STDERR.puts "Error connecting: #{ex.message}".color(:red)
96
+ STDERR.puts "Skipping user #{user}".color(:red)
97
+ next
98
+ end
73
99
 
74
- ret = rbox.send(command, args)
75
- if ret.exit_code > 0
76
- puts " Exit code: #{ret.exit_code}".color(:red)
77
- puts " STDERR: #{ret.stderr.join("#{$/} ")}".color(:red)
78
- puts " STDOUT: #{ret.stdout.join("#{$/} ")}".color(:red)
79
- else
80
- puts ' ' << ret.stdout.join("#{$/} ")
100
+ execute_rbox_command {
101
+ # We need to create the config file for every script,
102
+ # b/c the user may change and it would not be accessible.
103
+ # We turn off safe mode so we can write the config file via SSH.
104
+ # This will need to use SCP eventually; it is unsafe and error prone.
105
+ # TODO: Replace with rbox.upload. Make it safe again!
106
+ conf_str = StringIO.new
107
+ conf_str.puts sconf.to_hash.to_yaml
108
+ rbox.upload(conf_str, @@script_config_file)
109
+ rbox.chmod(600, @@script_config_file)
110
+ }
111
+
112
+ commands.each_pair do |command, calls|
113
+ # If a command is only referred to once and it has no arguments
114
+ # defined, we force it through by making an array with one element.
115
+ calls = [[]] if calls.empty?
116
+
117
+ # Force a CommandNotFound exception early
118
+ unless rbox.can?(command)
119
+ execute_rbox_command { rbox.send(command) }
120
+ next
121
+ end
122
+
123
+ # Execute the command for every set of arguments
124
+ calls.each do |args|
125
+ puts command_separator(rbox.preview_command(command, args), user)
126
+ execute_rbox_command { ret = rbox.send(command, args) }
81
127
  end
82
- rescue Rye::CommandNotFound => ex
83
- puts " CommandNotFound: #{ex.message}".color(:red)
84
128
  end
85
129
 
130
+ # I was gettings errors about script_config_file not existing. There
131
+ # might be a race condition when the rm command is called too quickly.
132
+ # It's also quite possible I'm off my rocker!
133
+ sleep 0.1
86
134
 
87
- rbox.rm(@@script_config_file)
135
+ rbox.cd # reset to home dir
136
+ #rbox.rm(@@script_config_file)
88
137
  end
138
+
139
+ # Return the borrowed rbox instance to the user it was provided with
89
140
  rbox.switch_user original_user
90
141
  else
91
- #puts "Nothing to do"
142
+ puts "Nothing to do"
92
143
  end
93
144
 
94
145
  tf.delete # delete local copy of script config
@@ -0,0 +1,37 @@
1
+
2
+ module Rudy; module Routines;
3
+ module UserHelper
4
+ include Rudy::Routines::HelperBase # TODO: use execute_rbox_command
5
+ extend self
6
+
7
+ def adduser?(routine)
8
+ (!routine.adduser.nil? && !routine.adduser.empty?)
9
+ end
10
+ def adduser(routine, machine, rbox)
11
+
12
+ # On Solaris, the user's home directory needs to be specified
13
+ # explicitly so we do it for linux too for fun.
14
+ homedir = rbox.guess_user_home(routine.adduser.to_s)
15
+ args = [:m, :d, homedir, :s, '/bin/bash', routine.adduser.to_s]
16
+ puts command_separator(rbox.preview_command(:useradd, args), routine.adduser.to_s)
17
+
18
+ # NOTE: We'll may to use platform specific code here.
19
+ # Linux has adduser and useradd commands:
20
+ # adduser can prompt for info which we don't want.
21
+ # useradd does not prompt (on Debian/Ubuntu at least).
22
+ # We need to specify bash b/c the default is /bin/sh
23
+ execute_rbox_command { rbox.useradd(args) }
24
+ end
25
+
26
+ def authorize?(routine)
27
+ (!routine.authorize.nil? && !routine.authorize.empty?)
28
+ end
29
+ def authorize(routine, machine, rbox)
30
+ puts command_separator(:authorize_keys_remote, routine.authorize)
31
+ execute_rbox_command { rbox.authorize_keys_remote(routine.authorize) }
32
+ end
33
+
34
+
35
+ end
36
+
37
+ end; end
@@ -0,0 +1,36 @@
1
+
2
+
3
+ module Rudy; module Routines;
4
+ class Passthrough < Rudy::Routines::Base
5
+
6
+ def init(*args)
7
+ @routine_name = args.first
8
+ @routine = fetch_routine_config(@routine_name)
9
+ end
10
+
11
+ # * +each_mach+ is an optional block which is executed between
12
+ # disk creation and the after scripts. The will receives two
13
+ # arguments: instances of Rudy::Machine and Rye::Box.
14
+ def execute(&each_mach)
15
+ routine_separator(@routine_name)
16
+ machines = []
17
+ generic_machine_runner(:list) do |machine|
18
+ puts $/ #, "[routine: #{@routine_name}]"
19
+ machines << machine
20
+ end
21
+ machines
22
+ end
23
+
24
+ # Called by generic_machine_runner
25
+ def raise_early_exceptions
26
+ raise Rudy::Error, "No routine name" unless @routine_name
27
+ raise NoRoutine, @routine_name unless @routine
28
+ rmach = Rudy::Machines.new
29
+ raise Rudy::PrivateKeyNotFound, root_keypairpath unless has_keypair?(:root)
30
+ raise MachineGroupNotDefined, current_machine_group unless known_machine_group?
31
+ raise MachineGroupNotRunning, current_machine_group unless rmach.running?
32
+ end
33
+
34
+ end
35
+
36
+ end; end