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
@@ -1,34 +1,50 @@
1
1
 
2
+
2
3
  module Rudy; module Routines;
3
4
  class Release < Rudy::Routines::Base
4
-
5
- def execute
6
- p find_scm(:release)
7
- end
8
5
 
6
+ def init(*args)
7
+ @routine_name = args.first || :release # :release or :rerelease
8
+ @routine = fetch_routine_config(@routine_name)
9
+ end
9
10
 
10
- private
11
- def find_scm(routine)
12
- env, rol, att = @@global.environment, @@global.role
11
+ def execute()
12
+ routine_separator(@routine_name)
13
13
 
14
- # Look for the source control engine, checking all known scm values.
15
- # The available one will look like [environment][role][release][svn]
16
- params = nil
17
- scm_name = nil
18
- SUPPORTED_SCM_NAMES.each do |v|
19
- scm_name = v
20
- params = @@config.routines.find(env, rol, routine, scm_name)
21
- break if params
14
+ vlist = []
15
+
16
+ # Some early version control system failing
17
+ if Rudy::Routines::SCMHelper.scm?(@routine)
18
+ vlist = Rudy::Routines::SCMHelper.create_scm_objects(@routine)
19
+ puts task_separator("CREATING RELEASE TAG#{'S' if vlist.size > 1}")
20
+ vlist.each do |scm|
21
+ scm.create_release(Rudy.sysinfo.user)
22
+ puts scm.liner_note
23
+ end
22
24
  end
23
-
24
- if params
25
- klass = eval "Rudy::SCM::#{scm_name.to_s.upcase}"
26
- scm = klass.new(:base => params[:base])
25
+
26
+ machines = []
27
+ generic_machine_runner(:list) do |machine,rbox|
28
+ vlist.each do |scm|
29
+ puts task_separator("CREATING REMOTE #{scm.engine.to_s.upcase} CHECKOUT")
30
+ scm.create_remote_checkout(rbox)
31
+ end
32
+ machines << machine
27
33
  end
28
-
29
- [scm, params]
30
-
34
+
35
+ machines
36
+ end
37
+
38
+ # Called by generic_machine_runner
39
+ def raise_early_exceptions
40
+ raise NoRoutine, :release unless @routine
41
+ rmach = Rudy::Machines.new
42
+ raise Rudy::PrivateKeyNotFound, root_keypairpath unless has_keypair?(:root)
43
+ raise MachineGroupNotDefined, current_machine_group unless known_machine_group?
44
+ raise MachineGroupNotRunning, current_machine_group unless rmach.running?
31
45
  end
32
46
 
47
+
48
+
33
49
  end
34
50
  end;end
@@ -1,62 +1,33 @@
1
1
 
2
2
 
3
3
  module Rudy; module Routines;
4
-
5
4
  class Shutdown < Rudy::Routines::Base
6
5
 
6
+ def init(*args)
7
+ @routine = fetch_routine_config(:shutdown)
8
+ end
9
+
7
10
  def execute
11
+ routine_separator(:shutdown)
12
+ unless @routine
13
+ STDERR.puts "[this is a generic shutdown routine]"
14
+ @routine = {}
15
+ end
16
+ machines = []
17
+ generic_machine_runner(:destroy) do |machine,rbox|
18
+ #puts task_separator("SHUTDOWN")
19
+ machines << machine
20
+ end
21
+ machines
22
+ end
23
+
24
+ # Called by generic_machine_runner
25
+ def raise_early_exceptions
8
26
  rmach = Rudy::Machines.new
9
27
  raise Rudy::PrivateKeyNotFound, root_keypairpath unless has_keypair?(:root)
10
- raise MachineGroupNotDefined, current_machine_group unless known_machine_group?
11
28
  raise MachineGroupNotRunning, current_machine_group unless rmach.running?
12
-
13
- routine = fetch_routine_config(:shutdown)
14
- rbox_local = Rye::Box.new('localhost')
15
- sconf = fetch_script_config
16
-
17
- # Runs "before_local" scripts of routines config.
18
- puts task_separator("BEFORE SCRIPTS (local)")
19
- Rudy::Routines::ScriptHelper.before_local(routine, sconf, rbox_local)
20
-
21
- puts
22
-
23
- rmach.destroy do |machine|
24
- #rmach.list do |machine|
25
-
26
- print "Waiting for instance..."
27
- isup = Rudy::Utils.waiter(3, 120, STDOUT, "it's up!", 0) {
28
- inst = machine.get_instance
29
- inst && inst.running?
30
- }
31
- machine.update # Add instance info to machine and save it
32
- print "Waiting for SSH daemon..."
33
- isup = Rudy::Utils.waiter(2, 60, STDOUT, "it's up!", 0) {
34
- Rudy::Utils.service_available?(machine.dns_public, 22)
35
- }
36
-
37
- opts = { :keys => root_keypairpath, :user => 'root', :debug => nil }
38
- rbox = Rye::Box.new(machine.dns_public, opts)
39
-
40
- # Runs "before" scripts of routines config.
41
- puts task_separator("BEFORE SCRIPTS")
42
- Rudy::Routines::ScriptHelper.before(routine, sconf, machine, rbox)
43
-
44
- # Runs "disk" portion of routines config
45
- puts task_separator("DISK ROUTINES")
46
- Rudy::Routines::DiskHelper.execute(routine, machine, rbox)
47
-
48
- puts machine_separator(machine.liner_note)
49
- end
50
-
51
-
52
- # Runs "after_local" scripts
53
- # NOTE: There "after" (remote) scripts are not run b/c the machines
54
- # are no longer running.
55
- puts task_separator("AFTER SCRIPTS (local)")
56
- Rudy::Routines::ScriptHelper.after_local(routine, sconf, rbox_local)
57
-
58
29
  end
59
-
30
+
60
31
  end
61
32
 
62
33
  end; end
@@ -1,61 +1,34 @@
1
1
 
2
2
 
3
3
  module Rudy; module Routines;
4
-
5
4
  class Startup < Rudy::Routines::Base
5
+
6
+ def init(*args)
7
+ @routine = fetch_routine_config(:startup)
8
+ end
9
+
10
+ # * +each_mach+ is an optional block which is executed between
11
+ # disk creation and the after scripts. The will receives two
12
+ # arguments: instances of Rudy::Machine and Rye::Box.
13
+ def execute(&each_mach)
14
+ routine_separator(:startup)
15
+ unless @routine
16
+ STDERR.puts "[this is a generic startup routine]"
17
+ @routine = {}
18
+ end
19
+ generic_machine_runner(:create) do |machine,rbox|
20
+ end
21
+ end
6
22
 
7
- def execute
23
+ # Called by generic_machine_runner
24
+ def raise_early_exceptions
8
25
  rmach = Rudy::Machines.new
9
26
  # There's no keypair check here because Rudy::Machines will attempt
10
27
  # to create one.
11
28
  raise MachineGroupNotDefined, current_machine_group unless known_machine_group?
12
29
  raise MachineGroupAlreadyRunning, current_machine_group if rmach.running?
13
-
14
- routine = fetch_routine_config(:startup)
15
- rbox_local = Rye::Box.new('localhost')
16
- sconf = fetch_script_config
17
-
18
- # Runs "before_local" scripts of routines config.
19
- # NOTE: Does not run "before" scripts b/c there are no remote machines
20
- puts task_separator("BEFORE SCRIPTS (local)")
21
- Rudy::Routines::ScriptHelper.before_local(routine, sconf, rbox_local)
22
-
23
- rmach.create do |machine|
24
- #rmach.list do |machine|
25
- puts machine_separator(machine.liner_note)
26
- print "Waiting for instance..."
27
- isup = Rudy::Utils.waiter(3, 120, STDOUT, "it's up!", 2) {
28
- inst = machine.get_instance
29
- inst && inst.running?
30
- }
31
- machine.update # Add instance info to machine and save it
32
- print "Waiting for SSH daemon..."
33
- isup = Rudy::Utils.waiter(2, 60, STDOUT, "it's up!", 3) {
34
- Rudy::Utils.service_available?(machine.dns_public, 22)
35
- }
36
-
37
- opts = { :keys => root_keypairpath, :user => 'root', :debug => nil }
38
- rbox = Rye::Box.new(machine.dns_public, opts)
39
-
40
- puts task_separator("DISK ROUTINES")
41
- # Runs "disk" portion of routines config
42
- Rudy::Routines::DiskHelper.execute(routine, machine, rbox)
43
-
44
- puts task_separator("AFTER SCRIPTS")
45
- # Runs "after" scripts of routines config
46
- Rudy::Routines::ScriptHelper.after(routine, sconf, machine, rbox)
47
-
48
- puts task_separator("INFO")
49
- puts "Filesystem on #{machine.name}:"
50
- puts " " << rbox.df(:h).join("#{$/} ")
51
- end
52
-
53
- puts task_separator("AFTER SCRIPTS (local)")
54
- # Runs "after_local" scripts of routines config
55
- Rudy::Routines::ScriptHelper.after_local(routine, sconf, rbox_local)
56
-
57
30
  end
58
-
31
+
59
32
  end
60
33
 
61
34
  end; end
data/lib/rudy/scm.rb ADDED
@@ -0,0 +1,75 @@
1
+
2
+
3
+ module Rudy
4
+ module SCM
5
+
6
+ class NotAWorkingCopy < Rudy::Error
7
+ def message
8
+ "Not the root directory of a #{@obj} working copy"
9
+ end
10
+ end
11
+ class CannotCreateTag < Rudy::Error
12
+ def message
13
+ "There was an unknown problem creating a release tag (#{@obj})"
14
+ end
15
+ end
16
+ class DirtyWorkingCopy < Rudy::Error
17
+ def message
18
+ "Please commit local #{@obj} changes"
19
+ end
20
+ end
21
+ class RemoteError < Rudy::Error; end
22
+ class NoRemoteURI < Rudy::Error; end
23
+ class TooManyTags < Rudy::Error
24
+ def message; "Too many tag creation attempts!"; end
25
+ end
26
+ class NoRemotePath < Rudy::Error
27
+ def message
28
+ "Add a path for #{@obj} in your routines config"
29
+ end
30
+ end
31
+
32
+
33
+ module ObjectBase
34
+
35
+
36
+ def raise_early_exceptions; raise "override raise_early_exceptions"; end
37
+
38
+ # copied from routines/helper.rb
39
+ def execute_rbox_command(ret=nil, &command)
40
+ begin
41
+ ret = command.call
42
+ puts ' ' << ret.stdout.join("#{$/} ") if !ret.stdout.empty?
43
+ print_response(ret)
44
+ rescue Rye::CommandError => ex
45
+ print_response(ex)
46
+ exit 12 unless keep_going?
47
+ rescue Rye::CommandNotFound => ex
48
+ STDERR.puts " CommandNotFound: #{ex.message}".color(:red)
49
+ STDERR.puts ex.backtrace
50
+ exit 12 unless keep_going?
51
+ end
52
+
53
+ ret
54
+ end
55
+
56
+
57
+ private
58
+ def keep_going?
59
+ Annoy.pose_question(" Keep going?\a ", /yes|y|ya|sure|you bet!/i, STDERR)
60
+ end
61
+
62
+ def print_response(rap)
63
+ [:stderr].each do |sumpin|
64
+ next if rap.send(sumpin).empty?
65
+ STDERR.puts " #{sumpin}: #{rap.send(sumpin).join("#{$/} ")}".color(:red)
66
+ end
67
+ STDERR.puts " Exit code: #{rap.exit_code}".color(:red) if rap.exit_code != 0
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+ end
74
+
75
+ Rudy::Utils.require_glob(RUDY_LIB, 'rudy', 'scm', '*.rb')
@@ -0,0 +1,215 @@
1
+
2
+ require 'date'
3
+
4
+
5
+ module Rudy
6
+ module SCM
7
+ class GIT
8
+ require 'grit'
9
+ include Rudy::SCM::ObjectBase
10
+ include Grit
11
+
12
+ attr_accessor :base_uri
13
+ attr_accessor :remote
14
+ attr_accessor :branch
15
+ attr_reader :repo
16
+ attr_reader :rbox
17
+ attr_reader :rtag
18
+ attr_reader :user
19
+ attr_reader :pkey
20
+
21
+ # * +args+ a hash of params from the git block in the routines config
22
+ #
23
+ def initialize(args={})
24
+ args = {
25
+ :privatekey => nil,
26
+ :remote => :origin,
27
+ :branch => :master,
28
+ :user => :root,
29
+ :path => nil
30
+ }.merge(args)
31
+ @remote, @branch, @path = args[:remote], args[:branch], args[:path]
32
+ @user, @pkey = args[:user], args[:privatekey]
33
+ @repo = Repo.new(Dir.pwd) if GIT.working_copy?
34
+ end
35
+
36
+ def engine; :git; end
37
+
38
+ def liner_note
39
+ "%-40s (git:%s:%s)" % [@rtag, @remote, @branch]
40
+ end
41
+
42
+ def create_release(username=nil, msg=nil)
43
+ @rtag = find_next_rtag(username)
44
+ msg ||= 'Another Release by Rudy!'
45
+ msg.tr!("'", "''")
46
+ ret = Rye.shell(:git, "tag", @rtag) # Use annotated? -a -m '#{msg}'
47
+ raise ret.stderr.join($/) if ret.exit_code > 0
48
+ ret = Rye.shell(:git, "push") if @remote
49
+ raise ret.stderr.join($/) if ret.exit_code > 0
50
+ ret = Rye.shell(:git, "push #{@remote} #{rtag}") if @remote
51
+ raise ret.stderr.join($/) if ret.exit_code > 0
52
+ @rtag
53
+ end
54
+
55
+ # rel-2009-03-05-user-rev
56
+ def find_next_rtag(username=nil)
57
+ now = Time.now
58
+ mon = now.mon.to_s.rjust(2, '0')
59
+ day = now.day.to_s.rjust(2, '0')
60
+ rev = "01"
61
+ criteria = ['rel', now.year, mon, day, rev]
62
+ criteria.insert(-2, username) if username
63
+ rev.succ! while valid_rtag?(criteria.join(Rudy::DELIM)) && rev.to_i < 50
64
+ raise TooManyTags if rev.to_i >= 50
65
+ criteria.join(Rudy::DELIM)
66
+ end
67
+
68
+ def delete_rtag(rtag=nil)
69
+ rtag ||= @rtag
70
+ ret = execute_rbox_command { Rye.shell(:git, 'tag', :d, rtag) }
71
+ raise ret.stderr.join($/) if ret.exit_code > 0 # TODO: retest
72
+ # Equivalent to: "git push origin :tag-name" which deletes a remote tag
73
+ ret = execute_rbox_command { Rye.shell(:git, "push #{@remote} :#{rtag}") } if @remote
74
+ raise ret.stderr.join($/) if ret.exit_code > 0
75
+ true
76
+ end
77
+
78
+ def create_remote_checkout(rbox)
79
+
80
+ # Make sure the directory above the clone path exists
81
+ # and that it's owned by the request user.
82
+ rbox.mkdir(:p, File.dirname(@path))
83
+ rbox.chown(@user, File.dirname(@path))
84
+
85
+ begin
86
+ original_user = rbox.user
87
+ rbox.switch_user(@user)
88
+
89
+ if @pkey
90
+ # Try when debugging: ssh -vi path/2/pkey git@github.com
91
+ key = File.basename(@pkey)
92
+ homedir = rbox.getenv['HOME']
93
+ rbox.mkdir(:p, :m, '700', '.ssh') rescue nil # :p says keep quiet if it exists
94
+ if rbox.file_exists?(".ssh/#{key}")
95
+ puts " Remote private key #{key} already exists".colour(:red)
96
+ else
97
+ rbox.upload(@pkey, ".ssh/#{key}")
98
+ end
99
+
100
+ # NOTE: The following are two attempts at telling git which
101
+ # private key to use. Both fail. The only thing I could get
102
+ # to work is modifying the ~/.ssh/config file.
103
+ #
104
+ # This runs fine, but "git clone" doesn't care.
105
+ # git config --global --replace-all http.sslKey /home/delano/.ssh/id_rsa
106
+ # rbox.git('config', '--global', '--replace-all', 'http.sslKey', "#{homedir}/.ssh/#{key}")
107
+
108
+ # "git clone" doesn't care about this either. Note that both these
109
+ # config attempts come directly from the git-config man page:
110
+ # http://www.kernel.org/pub/software/scm/git/docs/git-config.html
111
+ # export GIT_SSL_KEY=/home/delano/.ssh/id_rsa
112
+ # rbox.setenv("GIT_SSL_KEY", "#{homedir}/.ssh/#{key}")
113
+
114
+ if rbox.file_exists?('.ssh/config')
115
+ rbox.cp('.ssh/config', ".ssh/config-previous")
116
+ ssh_config = rbox.download('.ssh/config')
117
+ end
118
+
119
+ ssh_config ||= StringIO.new
120
+ ssh_config.puts $/, "IdentityFile #{homedir}/.ssh/#{key}"
121
+ puts " Adding IdentityFile #{key} to #{homedir}/.ssh/config"
122
+
123
+ rbox.upload(ssh_config, '.ssh/config')
124
+ rbox.chmod('0600', '.ssh/config')
125
+
126
+ end
127
+
128
+ # We need to add the host keys to the user's known_hosts file
129
+ # to prevent the git commands from failing when it raises the
130
+ # "Host key verification failed." messsage.
131
+ if rbox.file_exists?('.ssh/known_hosts')
132
+ rbox.cp('.ssh/known_hosts', ".ssh/known_hosts-previous")
133
+ known_hosts = rbox.download('.ssh/known_hosts')
134
+ end
135
+ known_hosts ||= StringIO.new
136
+ remote = get_remote_uri
137
+ host = URI.parse(remote).host rescue nil
138
+ host ||= remote.scan(/\A.+?@(.+?)\:/).flatten.first
139
+ known_hosts.puts $/, Rye.remote_host_keys(host)
140
+ puts " Adding host key for #{host} to .ssh/known_hosts"
141
+
142
+ rbox.upload(known_hosts, '.ssh/known_hosts')
143
+ rbox.chmod('0600', '.ssh/known_hosts')
144
+
145
+ execute_rbox_command {
146
+ rbox.git('clone', get_remote_uri, @path)
147
+ }
148
+ rbox.cd(@path)
149
+ execute_rbox_command {
150
+ rbox.git('checkout', :b, @rtag)
151
+ }
152
+ rescue Rye::CommandError => ex
153
+ puts ex.message
154
+ ensure
155
+ # Return to the original user and directory
156
+ rbox.switch_user(original_user)
157
+ rbox.cd
158
+ end
159
+
160
+ end
161
+
162
+
163
+ def get_remote_uri
164
+ ret = Rye.shell(:git, "config", "remote.#{@remote}.url")
165
+ ret.stdout.first
166
+ end
167
+
168
+ # Check if the given remote is valid.
169
+ #def has_remote?(remote)
170
+ # success = false
171
+ # (@repo.remotes || []).each do |r|
172
+ # end
173
+ # success
174
+ #end
175
+
176
+ def valid_rtag?(tag)
177
+ # git tag -l tagname returns a 0 exit code and stdout is empty
178
+ # when a tag does not exit. When it does exist, the exit code
179
+ # is 0 and stdout contains the tagname.
180
+ ret = Rye.shell(:git, 'tag', :l, tag)
181
+ # change :l to :d for quick deleting above and return true
182
+ # OR: just change to :d to always recreate the same tag
183
+ (ret.exit_code == 0 && ret.stdout.to_s == tag)
184
+ end
185
+
186
+ # Are all local changes committed?
187
+ def self.clean_working_copy?(path=Dir.pwd)
188
+ Rye.shell(:git, 'diff').stdout == []
189
+ end
190
+ def clean_working_copy?; GIT.clean_working_copy?; end
191
+
192
+ def self.working_copy?(path=Dir.pwd)
193
+ (File.exists?(File.join(path, '.git')))
194
+ end
195
+ def working_copy?; GIT.working_copy?; end
196
+
197
+ def raise_early_exceptions
198
+ raise NotAWorkingCopy, :git unless working_copy?
199
+ #raise DirtyWorkingCopy, :git unless clean_working_copy?
200
+ raise NoRemoteURI, "remote.#{@remote}.url not set" if get_remote_uri.nil?
201
+ raise NoRemotePath, :git if @path.nil?
202
+ raise PrivateKeyNotFound, @pkey if @pkey && !File.exists?(@pkey)
203
+ find_next_rtag # will raise exception is there's a problem
204
+
205
+ # We can't check stuff that requires access to the machine b/c the
206
+ # machine may not be running yet. These include:
207
+ # * Remote checkout path already exists
208
+ # * No git available
209
+ # ...
210
+ # If create_remote_checkout should fail, it should print a message
211
+ # about the release that was created and how to install it manually
212
+ end
213
+ end
214
+ end
215
+ end