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