nuex-vlad 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,117 @@
1
+ class Vlad::Perforce
2
+
3
+ set :p4_cmd, "p4"
4
+ set :source, Vlad::Perforce.new
5
+
6
+ ##
7
+ # Returns the p4 command that will checkout +revision+ into the directory
8
+ # +destination+.
9
+
10
+ def checkout(revision, destination)
11
+ "#{p4_cmd} sync ...#{rev_no(revision)}"
12
+ end
13
+
14
+ ##
15
+ # Returns the p4 command that will export +revision+ into the directory
16
+ # +directory+.
17
+
18
+ def export(revision_or_source, destination)
19
+ if revision_or_source =~ /^(\d+|head)$/i then
20
+ "(cd #{destination} && #{p4_cmd} sync ...#{rev_no(revision_or_source)})"
21
+ else
22
+ "cp -r #{revision_or_source} #{destination}"
23
+ end
24
+ end
25
+
26
+ ##
27
+ # Returns a command that maps human-friendly revision identifier +revision+
28
+ # into a Perforce revision specification.
29
+
30
+ def revision(revision)
31
+ "`#{p4_cmd} changes -s submitted -m 1 ...#{rev_no(revision)} | cut -f 2 -d\\ `"
32
+ end
33
+
34
+ ##
35
+ # Maps revision +revision+ into a Perforce revision.
36
+
37
+ def rev_no(revision)
38
+ case revision.to_s
39
+ when /head/i then
40
+ "#head"
41
+ when /^\d+$/ then
42
+ "@#{revision}"
43
+ else
44
+ revision
45
+ end
46
+ end
47
+ end
48
+
49
+ namespace :vlad do
50
+ remote_task :setup_app, :roles => :app do
51
+ p4data = p4port = p4user = p4passwd = nil
52
+
53
+ if ENV['P4CONFIG'] then
54
+ p4config_name = ENV['P4CONFIG']
55
+ p4config = nil
56
+ orig_dir = Dir.pwd.split File::SEPARATOR
57
+
58
+ until orig_dir.length == 1 do
59
+ p4config = orig_dir + [p4config_name]
60
+ p4config = File.join p4config
61
+ break if File.exist? p4config
62
+ orig_dir.pop
63
+ end
64
+
65
+ raise "couldn't find .p4config" unless File.exist? p4config
66
+
67
+ p4data = File.readlines(p4config).map { |line| line.strip.split '=', 2 }
68
+ p4data = Hash[*p4data.flatten]
69
+ else
70
+ p4data = ENV
71
+ end
72
+
73
+ p4port = p4data['P4PORT']
74
+ p4user = p4data['P4USER']
75
+ p4passwd = p4data['P4PASSWD']
76
+
77
+ raise "couldn't get P4PORT" if p4port.nil?
78
+ raise "couldn't get P4USER" if p4user.nil?
79
+ raise "couldn't get P4PASSWD" if p4passwd.nil?
80
+
81
+ p4client = [p4user, target_host, application].join '-'
82
+
83
+ require 'tmpdir'
84
+ require 'tempfile'
85
+
86
+ put File.join(scm_path, '.p4config'), 'vlad.p4config' do
87
+ [ "P4PORT=#{p4port}",
88
+ "P4USER=#{p4user}",
89
+ "P4PASSWD=#{p4passwd}",
90
+ "P4CLIENT=#{p4client}" ].join("\n")
91
+ end
92
+
93
+ p4client_path = File.join deploy_to, 'p4client.tmp'
94
+
95
+ put p4client_path, 'vlad.p4client' do
96
+ conf = <<-"CLIENT"
97
+ Client: #{p4client}
98
+
99
+ Owner: #{p4user}
100
+
101
+ Root: #{scm_path}
102
+
103
+ View:
104
+ #{repository}/... //#{p4client}/...
105
+ CLIENT
106
+ end
107
+
108
+ cmds = [
109
+ "cd #{scm_path}",
110
+ "p4 client -i < #{p4client_path}",
111
+ "rm #{p4client_path}",
112
+ ]
113
+
114
+ run cmds.join(' && ')
115
+ end
116
+ end
117
+
@@ -0,0 +1,35 @@
1
+ class Vlad::Subversion
2
+
3
+ set :source, Vlad::Subversion.new
4
+ set :svn_cmd, "svn"
5
+
6
+ ##
7
+ # Returns the command that will check out +revision+ from the repository
8
+ # into directory +destination+
9
+
10
+ def checkout(revision, destination)
11
+ "#{svn_cmd} co -r #{revision} #{repository} #{destination}"
12
+ end
13
+
14
+ ##
15
+ # Returns the command that will export +revision+ from the repository into
16
+ # the directory +destination+.
17
+
18
+ def export(revision_or_source, destination)
19
+ "#{svn_cmd} #{deploy_via} " +
20
+ if revision_or_source =~ /^(\d+|head)$/i then
21
+ "-r #{revision_or_source} #{repository} #{destination}"
22
+ else
23
+ "#{revision_or_source} #{destination}"
24
+ end
25
+ end
26
+
27
+ ##
28
+ # Returns a command that maps human-friendly revision identifier +revision+
29
+ # into a subversion revision specification.
30
+
31
+ def revision(revision)
32
+ "`#{svn_cmd} info #{repository} | grep 'Revision:' | cut -f2 -d\\ `"
33
+ end
34
+ end
35
+
data/lib/vlad/thin.rb ADDED
@@ -0,0 +1,63 @@
1
+ # $GEM_HOME/gems/vlad-1.2.0/lib/vlad/thin.rb
2
+ # Thin tasks for Vlad the Deployer
3
+ # By cnantais
4
+ require 'vlad'
5
+
6
+ namespace :vlad do
7
+ ##
8
+ # Thin app server
9
+
10
+ set :thin_address, "127.0.0.1"
11
+ set :thin_command, 'thin'
12
+ set(:thin_conf) { "#{shared_path}/thin_cluster.conf" }
13
+ set :thin_environment, "production"
14
+ set :thin_group, nil
15
+ set :thin_log_file, nil
16
+ set :thin_pid_file, nil
17
+ set :thin_port, nil
18
+ set :thin_socket, "/tmp/thin.sock"
19
+ set :thin_prefix, nil
20
+ set :thin_servers, 2
21
+ set :thin_user, nil
22
+ set :thin_rackup, nil
23
+
24
+ desc "Prepares application servers for deployment. thin
25
+ configuration is set via the thin_* variables.".cleanup
26
+
27
+ remote_task :setup_app, :roles => :app do
28
+ cmd = [
29
+ "#{thin_command} config",
30
+ "-s #{thin_servers}",
31
+ "-S #{thin_socket}",
32
+ "-e #{thin_environment}",
33
+ "-c #{current_path}",
34
+ "-C #{thin_conf}",
35
+ ("-a #{thin_address}" if thin_address),
36
+ ("-R #{thin_rackup}" if thin_rackup),
37
+ ("-P #{thin_pid_file}" if thin_pid_file),
38
+ ("-l #{thin_log_file}" if thin_log_file),
39
+ ("--user #{thin_user}" if thin_user),
40
+ ("--group #{thin_group}" if thin_group),
41
+ ("--prefix #{thin_prefix}" if thin_prefix),
42
+ ("-p #{thin_port}" if thin_port),
43
+ ].compact.join ' '
44
+
45
+ run cmd
46
+ end
47
+
48
+ def thin(cmd) # :nodoc:
49
+ "#{thin_command} #{cmd} -C #{thin_conf}"
50
+ end
51
+
52
+ desc "Restart the app servers"
53
+
54
+ remote_task :start_app, :roles => :app do
55
+ run thin("restart -s #{thin_servers}")
56
+ end
57
+
58
+ desc "Stop the app servers"
59
+
60
+ remote_task :stop_app, :roles => :app do
61
+ run thin("stop -s #{thin_servers}")
62
+ end
63
+ end
data/lib/vlad.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'rubygems'
2
+ require 'thread'
3
+ require 'rake_remote_task'
4
+
5
+ $TESTING ||= false
6
+
7
+ ##
8
+ # Vlad the Deployer - Pragmatic application deployment automation, without mercy.
9
+ #
10
+ # Please read doco/getting_started.txt or http://rubyhitsquad.com/
11
+ #
12
+ # === Basic scenario:
13
+ #
14
+ # 1. rake vlad:setup (first time only)
15
+ # 2. rake vlad:update
16
+ # 3. rake vlad:migrate (optional)
17
+ # 4. rake vlad:start
18
+
19
+ module Vlad
20
+
21
+ ##
22
+ # This is the version of Vlad you are running.
23
+ VERSION = '1.4.0'
24
+
25
+ ##
26
+ # Base error class for all Vlad errors.
27
+ class Error < RuntimeError; end
28
+
29
+ ##
30
+ # Raised when you have incorrectly configured Vlad.
31
+ class ConfigurationError < Error; end
32
+
33
+ ##
34
+ # Raised when a remote command fails.
35
+ class CommandFailedError < Error; end
36
+
37
+ ##
38
+ # Raised when an environment variable hasn't been set.
39
+ class FetchError < Error; end
40
+
41
+ ##
42
+ # Loads tasks file +tasks_file+ and various recipe styles as a hash
43
+ # of category/style pairs. Recipes default to:
44
+ #
45
+ # :app => :passenger
46
+ # :config => 'config/deploy.rb'
47
+ # :core => :core
48
+ # :scm => :subversion
49
+ # :web => :apache
50
+ #
51
+ # You can override individual values and/or set to nil to
52
+ # deactivate. :config will get loaded last to ensure that user
53
+ # variables override default values.
54
+ #
55
+ # And by all means, feel free to skip this entirely if it doesn't
56
+ # fit for you. All it does is a fancy-pants require. Require
57
+ # whatever files you need as you see fit straight from your
58
+ # Rakefile. YAY for simple and clean!
59
+ def self.load options = {}
60
+ options = {:config => options} if String === options
61
+ order = [:core, :app, :config, :scm, :web]
62
+ order += options.keys - order
63
+
64
+ recipes = {
65
+ :app => :passenger,
66
+ :config => 'config/deploy.rb',
67
+ :core => :core,
68
+ :scm => :subversion,
69
+ :web => :apache,
70
+ }.merge(options)
71
+
72
+ order.each do |flavor|
73
+ recipe = recipes[flavor]
74
+ next if recipe.nil? or flavor == :config
75
+ require "vlad/#{recipe}"
76
+ end
77
+
78
+ Kernel.load recipes[:config]
79
+ Kernel.load "config/deploy_#{ENV['to']}.rb" if ENV['to']
80
+ end
81
+ end
82
+
83
+ class String #:nodoc:
84
+ def cleanup
85
+ if ENV['FULL'] then
86
+ gsub(/\s+/, ' ').strip
87
+ else
88
+ self[/\A.*?\./]
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,257 @@
1
+ require 'test/vlad_test_case'
2
+ require 'vlad'
3
+
4
+ class TestRakeRemoteTask < VladTestCase
5
+ # TODO: move to minitest
6
+ def assert_silent
7
+ out, err = capture_io do
8
+ yield
9
+ end
10
+
11
+ assert_empty err
12
+ assert_empty out
13
+ end
14
+
15
+ def test_enhance
16
+ util_set_hosts
17
+ body = Proc.new { 5 }
18
+ task = @vlad.remote_task(:some_task => :foo, &body)
19
+ action = Rake::RemoteTask::Action.new(task, body)
20
+ assert_equal [action], task.remote_actions
21
+ assert_equal task, action.task
22
+ assert_equal ["foo"], task.prerequisites
23
+ end
24
+
25
+ def test_enhance_with_no_task_body
26
+ util_set_hosts
27
+ util_setup_task
28
+ assert_equal [], @task.remote_actions
29
+ assert_equal [], @task.prerequisites
30
+ end
31
+
32
+ def test_execute
33
+ util_set_hosts
34
+ set :some_variable, 1
35
+ x = 5
36
+ task = @vlad.remote_task(:some_task) { x += some_variable }
37
+ task.execute nil
38
+ assert_equal 1, task.some_variable
39
+ assert_equal 7, x
40
+ end
41
+
42
+ def test_set_false
43
+ set :can_set_nil, nil
44
+ set :lies_are, false
45
+
46
+ assert_equal nil, task.can_set_nil
47
+
48
+ assert_equal false, task.lies_are
49
+ assert_equal false, Rake::RemoteTask.fetch(:lies_are)
50
+ end
51
+
52
+
53
+ def test_fetch_false
54
+ assert_equal false, Rake::RemoteTask.fetch(:unknown, false)
55
+ end
56
+
57
+ def test_execute_exposes_target_host
58
+ host "app.example.com", :app
59
+ task = remote_task(:target_task) { set(:test_target_host, target_host) }
60
+ task.execute nil
61
+ assert_equal "app.example.com", Rake::RemoteTask.fetch(:test_target_host)
62
+ end
63
+
64
+ def test_execute_with_no_hosts
65
+ @vlad.host "app.example.com", :app
66
+ t = @vlad.remote_task(:flunk, :roles => :db) { flunk "should not have run" }
67
+ e = assert_raises(Vlad::ConfigurationError) { t.execute nil }
68
+ assert_equal "No target hosts specified on task flunk for roles [:db]",
69
+ e.message
70
+ end
71
+
72
+ def test_execute_with_no_roles
73
+ t = @vlad.remote_task(:flunk, :roles => :junk) { flunk "should not have run" }
74
+ e = assert_raises(Vlad::ConfigurationError) { t.execute nil }
75
+ assert_equal "No target hosts specified on task flunk for roles [:junk]",
76
+ e.message
77
+ end
78
+
79
+ def test_execute_with_roles
80
+ util_set_hosts
81
+ set :some_variable, 1
82
+ x = 5
83
+ task = @vlad.remote_task(:some_task, :roles => :db) { x += some_variable }
84
+ task.execute nil
85
+ assert_equal 1, task.some_variable
86
+ assert_equal 6, x
87
+ end
88
+
89
+ def test_rsync
90
+ util_setup_task
91
+ @task.target_host = "app.example.com"
92
+
93
+ assert_silent do
94
+ @task.rsync 'localfile', 'host:remotefile'
95
+ end
96
+
97
+ commands = @task.commands
98
+
99
+ assert_equal 1, commands.size, 'not enough commands'
100
+ assert_equal(%w[rsync -azP --delete localfile host:remotefile],
101
+ commands.first)
102
+ end
103
+
104
+ def test_rsync_fail
105
+ util_setup_task
106
+ @task.target_host = "app.example.com"
107
+ @task.action = lambda { false }
108
+
109
+ e = assert_raises Vlad::CommandFailedError do
110
+ assert_silent do
111
+ @task.rsync 'local', 'host:remote'
112
+ end
113
+ end
114
+ exp = "execution failed: rsync -azP --delete local host:remote"
115
+ assert_equal exp, e.message
116
+ end
117
+
118
+ def test_rsync_deprecation
119
+ util_setup_task
120
+ @task.target_host = "app.example.com"
121
+
122
+ out, err = capture_io do
123
+ @task.rsync 'localfile', 'remotefile'
124
+ end
125
+
126
+ commands = @task.commands
127
+
128
+ assert_equal 1, commands.size, 'not enough commands'
129
+ assert_equal(%w[rsync -azP --delete localfile app.example.com:remotefile],
130
+ commands.first)
131
+
132
+ assert_equal("rsync deprecation: pass target_host:remote_path explicitly\n",
133
+ err)
134
+ assert_empty out
135
+ # flunk "not yet"
136
+ end
137
+
138
+ def test_get
139
+ util_setup_task
140
+ @task.target_host = "app.example.com"
141
+
142
+ assert_silent do
143
+ @task.get 'tmp', "remote1", "remote2"
144
+ end
145
+
146
+ commands = @task.commands
147
+
148
+ expected = %w[rsync -azP --delete app.example.com:remote1 app.example.com:remote2 tmp]
149
+
150
+ assert_equal 1, commands.size
151
+ assert_equal expected, commands.first
152
+ end
153
+
154
+ def test_put
155
+ util_setup_task
156
+ @task.target_host = "app.example.com"
157
+
158
+ assert_silent do
159
+ @task.put 'dest' do
160
+ "whatever"
161
+ end
162
+ end
163
+
164
+ commands = @task.commands
165
+
166
+ expected = %w[rsync -azP --delete HAPPY app.example.com:dest]
167
+ commands.first[3] = 'HAPPY'
168
+
169
+ assert_equal 1, commands.size
170
+ assert_equal expected, commands.first
171
+ end
172
+
173
+ def test_run
174
+ util_setup_task
175
+ @task.output << "file1\nfile2\n"
176
+ @task.target_host = "app.example.com"
177
+ result = nil
178
+
179
+ out, err = capture_io do
180
+ result = @task.run("ls")
181
+ end
182
+
183
+ commands = @task.commands
184
+
185
+ assert_equal 1, commands.size, 'not enough commands'
186
+ assert_equal ["ssh", "app.example.com", "ls"],
187
+ commands.first, 'app'
188
+ assert_equal "file1\nfile2\n", result
189
+
190
+ assert_equal "file1\nfile2\n", out
191
+ assert_equal '', err
192
+ end
193
+
194
+ def test_run_failing_command
195
+ util_set_hosts
196
+ util_setup_task
197
+ @task.input = StringIO.new "file1\nfile2\n"
198
+ @task.target_host = 'app.example.com'
199
+ @task.action = lambda { 1 }
200
+
201
+ e = assert_raises(Vlad::CommandFailedError) { @task.run("ls") }
202
+ assert_equal "execution failed with status 1: ssh app.example.com ls", e.message
203
+
204
+ assert_equal 1, @task.commands.size
205
+ end
206
+
207
+ def test_run_sudo
208
+ util_setup_task
209
+ @task.output << "file1\nfile2\n"
210
+ @task.error << 'Password:'
211
+ @task.target_host = "app.example.com"
212
+ def @task.sudo_password() "my password" end # gets defined by set
213
+ result = nil
214
+
215
+ out, err = capture_io do
216
+ result = @task.run("sudo ls")
217
+ end
218
+
219
+ commands = @task.commands
220
+
221
+ assert_equal 1, commands.size, 'not enough commands'
222
+ assert_equal ['ssh', 'app.example.com', 'sudo ls'],
223
+ commands.first
224
+
225
+ assert_equal "my password\n", @task.input.string
226
+
227
+ # WARN: Technically incorrect, the password line should be
228
+ # first... this is an artifact of changes to the IO code in run
229
+ # and the fact that we have a very simplistic (non-blocking)
230
+ # testing model.
231
+ assert_equal "file1\nfile2\nPassword:\n", result
232
+
233
+ assert_equal "file1\nfile2\n", out
234
+ assert_equal "Password:\n", err
235
+ end
236
+
237
+ def test_sudo
238
+ util_setup_task
239
+ @task.target_host = "app.example.com"
240
+ @task.sudo "ls"
241
+
242
+ commands = @task.commands
243
+
244
+ assert_equal 1, commands.size, 'wrong number of commands'
245
+ assert_equal ["ssh", "app.example.com", "sudo -p Password: ls"],
246
+ commands.first, 'app'
247
+ end
248
+
249
+ def util_setup_task(options = {})
250
+ @task = @vlad.remote_task :test_task, options
251
+ @task.commands = []
252
+ @task.output = []
253
+ @task.error = []
254
+ @task.action = nil
255
+ @task
256
+ end
257
+ end