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
@@ -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