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
@@ -6,11 +6,23 @@ module Rudy
6
6
 
7
7
 
8
8
  def machines
9
+ # Rudy::Machines.list takes two optional args for adding or
10
+ # removing metadata attributes to modify the select query.
11
+ # When all is specified we want to find machines in every
12
+ # environment and role to we remove these attributes from
13
+ # the select.
14
+ more, less = nil, nil
15
+ less = [:environment, :role] if @option.all
16
+
9
17
  rmach = Rudy::Machines.new
10
- mlist = rmach.list || []
18
+ mlist = rmach.list(more, less) || []
11
19
  if mlist.empty?
12
- puts "No machines running in #{current_machine_group}"
13
- puts "Try: #{$0} startup"
20
+ if @option.all
21
+ puts "No machines running"
22
+ else
23
+ puts "No machines running in #{current_machine_group}"
24
+ puts "Try: rudy machines --all"
25
+ end
14
26
  end
15
27
  mlist.each do |m|
16
28
  puts @@global.verbose > 0 ? m.inspect : m.dump(@@global.format)
@@ -25,8 +37,8 @@ module Rudy
25
37
  return
26
38
  end
27
39
 
28
- puts "The following machine metadata will be deleted:"
29
- puts dirt.collect {|m| m.name }
40
+ puts "The following machine metadata will be deleted:".bright
41
+ puts dirt.collect {|m| m.name.bright }
30
42
  execute_check(:medium)
31
43
 
32
44
  dirt.each do |m|
@@ -44,7 +56,7 @@ module Rudy
44
56
  end
45
57
 
46
58
  # Options to be sent to Net::SSH
47
- ssh_opts = { :user => @@global.user || Rudy.sysinfo.user, :debug => nil }
59
+ ssh_opts = { :user => @@global.user || Rudy.sysinfo.user, :debug => nil }
48
60
  if pkey
49
61
  raise "Cannot find file #{pkey}" unless File.exists?(pkey)
50
62
  raise InsecureKeyPermissions, @pkey unless File.stat(pkey).mode == 33152
@@ -66,18 +78,26 @@ module Rudy
66
78
 
67
79
  checked = false
68
80
  rudy = Rudy::Machines.new
69
- lt = rudy.list do |machine|
81
+ lt = rudy.list
82
+ unless lt
83
+ puts "No machines running in #{rudy.current_machine_group}"
84
+ exit
85
+ end
86
+ lt.each do |machine|
87
+ machine.update # make sure we have the latest DNS info
88
+
70
89
  # Print header
71
90
  if @@global.quiet
72
91
  print "You are #{ssh_opts[:user].to_s.bright}. " if !checked # only the 1st
73
92
  else
74
- print "Connecting #{ssh_opts[:user].to_s.bright}@#{machine.dns_public} "
75
- puts "#{machine.name} (#{machine.awsid})"
93
+ puts machine_separator(machine.name, machine.awsid)
94
+ puts "Connecting #{ssh_opts[:user].to_s.bright}@#{machine.dns_public} "
95
+ puts
76
96
  end
77
97
 
78
98
  # Make sure we want to run this command on all instances
79
99
  if !checked && command != :interactive_ssh
80
- execute_check(:medium) if ssh_opts[:user] == "root"
100
+ execute_check(:low) if ssh_opts[:user] == "root"
81
101
  checked = true
82
102
  end
83
103
 
@@ -2,10 +2,15 @@
2
2
 
3
3
  module Rudy; module CLI;
4
4
  class Routines < Rudy::CLI::CommandBase
5
-
5
+
6
+ def startup_valid?
7
+ @rr = Rudy::Routines::Startup.new
8
+ @rr.raise_early_exceptions
9
+ true
10
+ end
6
11
  def startup
7
- rr = Rudy::Routines::Startup.new
8
- rr.execute
12
+
13
+ @rr.execute
9
14
 
10
15
  puts $/, "The following machines are now available:"
11
16
  rmach = Rudy::Machines.new
@@ -15,40 +20,83 @@ module Rudy; module CLI;
15
20
 
16
21
  if @@global.environment == @@config.defaults.environment &&
17
22
  @@global.role == @@config.defaults.role
18
- puts
19
- puts "Try: #{$0} -u root ssh"
23
+ #puts
24
+ #puts "Login with: rudy -u root ssh"
20
25
  end
21
26
 
22
27
  end
23
28
 
29
+ def release_valid?
30
+ @rr = Rudy::Routines::Release.new
31
+ @rr.raise_early_exceptions
32
+ true
33
+ end
24
34
  def release
25
- rr = Rudy::Routines::Release.new
26
- rmach = Rudy::Machines.new
27
- startup unless rmach.running?
28
- rr.execute
35
+ machines = @rr.execute
36
+
37
+ unless machines.empty?
38
+ puts $/, "The following machines were processed:"
39
+ machines.each do |machine|
40
+ puts machine.to_s
41
+ end
42
+ end
43
+ end
44
+
45
+ def passthrough_valid?
46
+ @rr = Rudy::Routines::Passthrough.new(@alias)
47
+ @rr.raise_early_exceptions
48
+ true
49
+ end
50
+
51
+ # All unknown commands are sent here (using Drydock's trawler).
52
+ # By default, the generic passthrough routine is executed which
53
+ # does nothing other than execute the routine config block that
54
+ # matches +@alias+ (the name used on the command-line). Calling
55
+ #
56
+ # $ rudy unknown
57
+ #
58
+ # would end up here because it's an unknown command. Passthrough
59
+ # then looks for a routine config in the current environment and
60
+ # role called "unknown". If found, it's executed otherwise it'll
61
+ # raise an exception.
62
+ #
63
+ def passthrough
64
+ machines = @rr.execute
65
+
66
+ unless machines.empty?
67
+ puts $/, "The following machines were processed:"
68
+ machines.each do |machine|
69
+ puts @@global.verbose > 0 ? machine.inspect : machine.dump(@@global.format)
70
+ end
71
+ end
72
+
29
73
  end
30
74
 
75
+ def shutdown_valid?
76
+ @rr = Rudy::Routines::Shutdown.new
77
+ @rr.raise_early_exceptions
78
+ true
79
+ end
31
80
  def shutdown
32
- rr = Rudy::Routines::Shutdown.new
33
81
  routine = fetch_routine_config(:shutdown)
34
-
35
- puts "All machines in #{current_machine_group} will be shutdown and"
82
+
83
+ puts "All machines in #{current_machine_group} will be shutdown".bright
36
84
  if routine && routine.disks
37
85
  if routine.disks.destroy
38
- puts "the following filesystems will be destroyed:".color(:red)
86
+ puts "The following filesystems will be destroyed:".bright
39
87
  puts routine.disks.destroy.keys.join($/).bright
40
88
  end
41
89
  end
42
90
 
43
91
  execute_check :medium
44
92
 
45
- rr.execute
46
-
47
- rinst = Rudy::AWS::EC2::Instances.new(@@global.accesskey, @@global.secretkey, @@global.region)
48
- lt = rinst.list_group(current_machine_group, :any) do |inst|
49
- puts @@global.verbose > 0 ? inst.inspect : inst.dump(@@global.format)
93
+ machines = @rr.execute
94
+ puts $/, "The following instances have been destroyed:"
95
+ machines.each do |machine|
96
+ puts '%s %s ' % [machine.name.bright, machine.awsid]
50
97
  end
51
- puts "No instances running" if !lt || lt.empty?
98
+
99
+
52
100
  end
53
101
 
54
102
 
data/lib/rudy/config.rb CHANGED
@@ -5,19 +5,28 @@ module Rudy
5
5
  class Config < Caesars::Config
6
6
  require 'rudy/config/objects'
7
7
 
8
- dsl Rudy::Config::Accounts::DSL
9
- dsl Rudy::Config::Defaults::DSL
10
- dsl Rudy::Config::Routines::DSL
11
- dsl Rudy::Config::Machines::DSL
12
- dsl Rudy::Config::Networks::DSL
8
+ dsl Rudy::Config::Accounts::DSL
9
+ dsl Rudy::Config::Defaults::DSL
10
+ dsl Rudy::Config::Routines::DSL # Organized processes
11
+ dsl Rudy::Config::Machines::DSL # Organized instances
12
+ dsl Rudy::Config::Commands::DSL # Custom SSH commands
13
+ #dsl Rudy::Config::Networks::DSL # Network design
14
+ #dsl Rudy::Config::Controls::DSL # Network access
15
+ #dsl Rudy::Config::Services::DSL # Stuff running on ports
13
16
 
14
- # TODO: auto-generate in caesars
15
- def accounts?; self.respond_to?(:accounts) && !self[:accounts].nil?; end
16
- def defaults?; self.respond_to?(:defaults) && !self[:defaults].nil?; end
17
- def machines?; self.respond_to?(:machines) && !self[:machines].nil?; end
18
- def routines?; self.respond_to?(:routines) && !self[:routines].nil?; end
19
- def networks?; self.respond_to?(:networks) && !self[:networks].nil?; end
17
+ def accounts?; self.respond_to?(:accounts) && !self[:accounts].nil?; end #a
18
+ def defaults?; self.respond_to?(:defaults) && !self[:defaults].nil?; end #u
19
+ def machines?; self.respond_to?(:machines) && !self[:machines].nil?; end #t
20
+ def routines?; self.respond_to?(:routines) && !self[:routines].nil?; end #o
21
+ def networks?; self.respond_to?(:networks) && !self[:networks].nil?; end #g
22
+ def controls?; self.respond_to?(:controls) && !self[:controls].nil?; end #e
23
+ def commands?; self.respond_to?(:commands) && !self[:commands].nil?; end #n
24
+ def services?; self.respond_to?(:services) && !self[:services].nil?; end #!
20
25
 
26
+ # This method is called by Caesars::Config.refresh for every DSL
27
+ # file that is loaded and parsed. If we want to run processing
28
+ # for a particular config (machines, @routines, etc...) we can
29
+ # do it here. Just wait until the instance variable is not nil.
21
30
  def postprocess
22
31
  #raise "There is no AWS info configured" if self.accounts.nil?
23
32
 
@@ -26,6 +35,15 @@ module Rudy
26
35
  # self.accounts.aws.cert &&= File.expand_path(self.accounts.aws.cert)
27
36
  # self.accounts.aws.privatekey &&= File.expand_path(self.accounts.aws.privatekey)
28
37
  #end
38
+
39
+ # The commands config modifies the way the routines configs
40
+ # should be parsed. This happens in the postprocess method
41
+ # we call here. We can't guarantee this will run before the
42
+ # routines config is loaded so this postprocess method will
43
+ # raise a Caesars::Config::ForceRefresh exception if that's
44
+ # the case. Rudy::Config::Commands knows to only raise the
45
+ # exception one time (using a boolean flag in a class var).
46
+ @commands.postprocess if @commands
29
47
  end
30
48
 
31
49
  def look_and_load(adhoc_path=nil)
@@ -77,8 +95,7 @@ module Rudy
77
95
  end
78
96
  end
79
97
 
80
- # Global Defaults
81
- # Define the values to use unless otherwise specified on the command-line.
98
+ # Global Defaults
82
99
  defaults do
83
100
  region :"us-east-1"
84
101
  zone :"us-east-1b"
@@ -1,29 +1,154 @@
1
1
 
2
2
 
3
3
  class Rudy::Config
4
- class Machines < Caesars
4
+ class Error < Rudy::Error
5
+ def initialize(ctype, obj)
6
+ @ctype, @obj = ctype, obj
7
+ end
8
+ def message; "Error in #{@ctype}: #{@obj}"; end
9
+ end
10
+ class Machines < Caesars; end
11
+ class Defaults < Caesars; end
12
+ class Networks < Caesars; end
13
+ class Controls < Caesars; end
14
+ class Services < Caesars; end
15
+ # Modify the SSH command available in routines. The default
16
+ # set of commands is defined by Rye::Cmd (Rudy executes all
17
+ # SSH commands via Rye).
18
+ #
19
+ # NOTE: We allow people to define their own keywords. It is
20
+ # important that new keywords do not conflict with existing
21
+ # Rudy keywords. Strange things may happen!
22
+ class Commands < Caesars
23
+ class PathNotString < Rudy::Config::Error
24
+ def message; super << " (path must be a String)"; end
25
+ end
26
+ class ReservedKeyword < Rudy::Config::Error
27
+ def message; "%s (reserved keyword)" % [super]; end
28
+ end
29
+ class BadArg < Rudy::Config::Error
30
+ def message; "Arguments for #{cmd} must be: Symbols, Strings only"; end
31
+ end
32
+
33
+ @@processed = false
34
+ forced_array :allow
35
+ forced_array :deny
36
+ def init
37
+ # We can't process the Rye::Cmd commands here because the
38
+ # DSL hasn't been parsed yet so Rudy::Config.postprocess
39
+ # called the following postprocess method after parsing.
40
+ end
41
+ # Process the directives specified in the commands config.
42
+ # NOTE: This affects the processing of the routines config
43
+ # which only works if commands is parsed first. This works
44
+ # naturally if each config has its own file b/c Rudy loads
45
+ # files via a glob (globs are alphabetized and "commands"
46
+ # comes before "routines").
47
+ #
48
+ # That's obviously not good enough but for now commands
49
+ # configuration MUST be put before routines.
50
+ def postprocess
51
+ return false if @@processed
52
+ @@processed = true # Make sure this runs only once
53
+ # Parses:
54
+ # commands do
55
+ # allow :kill
56
+ # allow :custom_script, '/full/path/2/custom_script'
57
+ # allow :git_clone, "/usr/bin/git", "clone"
58
+ # end
59
+ #
60
+ # * Tells Routines to force_array on the command name.
61
+ # This is important b/c of the way we parse commands
62
+ self.allow.each do |cmd|
63
+ cmd, path, *args = *cmd
64
+
65
+ # If no path was specified, we can assume cmd is in the remote path so
66
+ # when we add the method to Rye::Cmd, we'll it the path is "cmd".
67
+ path ||= cmd.to_s
68
+
69
+ # We cannot allow new commands to be defined that conflict use known
70
+ # routines keywords. This is based on keywords in the current config.
71
+ # NOTE: We can't check for this right now b/c the routines config
72
+ # won't necessarily have been parsed yet. TODO: Figure it out!
73
+ #if Caesars.known_symbol_by_glass?(:routines, cmd)
74
+ # raise ReservedKeyword.new(:commands, cmd)
75
+ #end
76
+
77
+ # We can allow existing commands to be overridden but we
78
+ # print a message to STDERR so the user knows what's up.
79
+ STDERR.puts "Redefined #{cmd}" if Rye::Cmd.can?(cmd)
80
+
81
+ # The second argument if supplied must be a filesystem path
82
+ raise PathNotString.new(:commands, cmd) if path && !path.is_a?(String)
83
+
84
+ # Insert hardcoded arguments if any were supplied. These will
85
+ # be sent automatically with every call to the new command.
86
+ # This loop prepares the hardcoded args to be passed to eval.
87
+ args.collect! do |arg|
88
+ klass = [Symbol, String] & [arg.class]
89
+ raise BadArg.new(:commands, cmd) if klass.empty?
90
+ # Symbols sent as Symbols, Strings as Strings
91
+ arg.is_a?(Symbol) ? ":#{arg}" : "'#{arg}'"
92
+ end
93
+ hard_args = args.empty? ? "*args" : "#{args.join(', ')}, *args"
94
+
95
+ # Command keywords must be parsed with forced_array. See ScriptHelper.
96
+ Rudy::Config::Routines.forced_array cmd
97
+ Rye::Cmd.module_eval %Q{
98
+ def #{cmd}(*args); cmd(:'#{path}', #{hard_args}); end
99
+ }
100
+ end
101
+ # We deny commands by telling routines to not parse the given keywords
102
+ self.deny.each do |cmd|
103
+ Rudy::Config::Routines.forced_ignore cmd.first # cmd is a forced array
104
+ # We don't remove the method from Rye:Cmd because we
105
+ # may need elsewhere in Rudy. Forced ignore ensures
106
+ # the config is not stored anyhow.
107
+ end
108
+ raise Caesars::Config::ForceRefresh.new(:routines)
109
+ end
5
110
  end
6
-
7
111
 
8
112
  class Accounts < Caesars
9
113
  def valid?
10
114
  (!aws.nil? && !aws.accesskey.nil? && !aws.secretkey.nil?) &&
11
- (!aws.account.empty? && !aws.accesskey.empty? && !aws.secretkey.empty?)
115
+ (!aws.account.empty? && !aws.accesskey.empty? && !aws.secretkey.empty?)
12
116
  end
13
117
  end
14
-
15
- class Defaults < Caesars
16
- end
17
-
18
- class Networks < Caesars
19
- end
20
118
 
21
119
  class Routines < Caesars
22
120
 
121
+ # Disk routines
23
122
  forced_hash :create
24
123
  forced_hash :destroy
25
124
  forced_hash :restore
26
125
  forced_hash :mount
27
-
126
+
127
+ # Remote scripts
128
+ forced_hash :before
129
+ forced_hash :before_local
130
+ forced_hash :after
131
+ forced_hash :after_local
132
+
133
+ # Version control systems
134
+ forced_hash :git
135
+ forced_hash :svn
136
+
137
+ def init
138
+
139
+ end
140
+
141
+ # Add remote shell commands to the DSL as forced Arrays.
142
+ # Example:
143
+ # ls :a, :l, "/tmp" # => :ls => [[:a, :l, "/tmp"]]
144
+ # ls :o # => :ls => [[:a, :l, "/tmp"], [:o]]
145
+ # NOTE: Beware of namespace conflicts in other areas of the DSL,
146
+ # specifically shell commands that have the same name as a keyword
147
+ # we want to use in the DSL. This includes commands that were added
148
+ # to Rye::Cmd before Rudy is 'require'd.
149
+ Rye::Cmd.instance_methods.sort.each do |cmd|
150
+ forced_array cmd
151
+ end
152
+
28
153
  end
29
154
  end
data/lib/rudy/disks.rb CHANGED
@@ -46,7 +46,8 @@ class Disk < Storable
46
46
 
47
47
  def to_s(with_titles=true)
48
48
  update
49
- mtd = @mounted ? "mounted" : @status
49
+ puts "FUCK" if @mounted
50
+ mtd = @mounted == true ? "mounted" : @status
50
51
  "%s; %3sGB; %s; %s" % [liner_note, @size, @device, mtd]
51
52
  end
52
53
 
@@ -103,12 +104,16 @@ class Disk < Storable
103
104
  end
104
105
 
105
106
  def update
106
- return false unless @awsid
107
- @volume = @rvol.get(@awsid)
107
+ @awsid = nil if @awsid && @awsid.empty?
108
+ @volume = @rvol.get(@awsid) if @awsid
108
109
  if @volume.is_a?(Rudy::AWS::EC2::Volume)
109
110
  @status = @volume.status
110
111
  @instid = @volume.instid
111
112
  save
113
+ else
114
+ @awsid, @status, @instid = nil, nil, nil
115
+ @mounted = false # I don't like having to set this
116
+ # Don't save it b/c it's possible the EC2 server is just down
112
117
  end
113
118
  end
114
119
 
@@ -197,52 +202,3 @@ class Disks
197
202
  end
198
203
  end
199
204
 
200
-
201
-
202
-
203
- __END__
204
-
205
- def format(instance)
206
- raise "No instance supplied" unless instance
207
- raise "Disk not valid" unless self.valid?
208
-
209
- begin
210
- puts "Creating the filesystem (mkfs.ext3 -F #{disk.device})".bright
211
- ssh_command instance.dns_public, current_user_keypairpath, @@global.user, "mkfs.ext3 -F #{disk.device}"
212
- sleep 1
213
- rescue => ex
214
- @logger.puts ex.backtrace if debug?
215
- raise "Error formatting #{disk.path}: #{ex.message}"
216
- end
217
- true
218
- end
219
- def mount(instance)
220
- raise "No instance supplied" unless instance
221
- disk = find_disk(opts[:disk] || opts[:path])
222
- raise "Disk #{opts[:disk] || opts[:path]} cannot be found" unless disk
223
- switch_user(:root)
224
- begin
225
- puts "Mounting #{disk.device} to #{disk.path}".bright
226
- ssh_command instance.dns_public, current_user_keypairpath, @@global.user, "mkdir -p #{disk.path} && mount -t ext3 #{disk.device} #{disk.path}"
227
- rescue => ex
228
- @logger.puts ex.backtrace if debug?
229
- raise "Error mounting #{disk.path}: #{ex.message}"
230
- end
231
- true
232
- end
233
-
234
- def unmount(instance)
235
- raise "No instance supplied" unless instance
236
- disk = find_disk(opts[:disk] || opts[:path])
237
- raise "Disk #{opts[:disk] || opts[:path]} cannot be found" unless disk
238
- switch_user(:root)
239
- begin
240
- puts "Unmounting #{disk.path}...".bright
241
- ssh_command instance.dns_public, current_user_keypairpath, global.user, "umount #{disk.path}"
242
- sleep 1
243
- rescue => ex
244
- @logger.puts ex.backtrace if debug?
245
- raise "Error unmounting #{disk.path}: #{ex.message}"
246
- end
247
- true
248
- end