gabrielg-vlad 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ class Vlad::Mercurial
2
+
3
+ set :source, Vlad::Mercurial.new
4
+
5
+ ##
6
+ # Returns the command that will check out +revision+ from the repository
7
+ # into directory +destination+
8
+
9
+ def checkout(revision, destination)
10
+ revision = 'tip' if revision =~ /^head$/i
11
+
12
+ [ "if [ ! -d #{destination}/.hg ]; then hg init -R #{destination}; fi",
13
+ "hg pull -r #{revision} -R #{destination} #{repository}"
14
+ ].join(' && ')
15
+ end
16
+
17
+ ##
18
+ # Returns the command that will export +revision+ from the repository into
19
+ # the directory +destination+.
20
+
21
+ def export(revision_or_source, destination)
22
+ revision_or_source = 'tip' if revision_or_source =~ /^head$/i
23
+ if revision_or_source =~ /^(\d+|tip)$/i then
24
+ "hg archive -r #{revision_or_source} -R #{repository} #{destination}"
25
+ else
26
+ "hg archive -R #{revision_or_source} #{destination}"
27
+ end
28
+ end
29
+
30
+ ##
31
+ # Returns a command that maps human-friendly revision identifier +revision+
32
+ # into a subversion revision specification.
33
+
34
+ def revision(revision)
35
+ "`hg identify -R #{repository} | cut -f1 -d\\ `"
36
+ end
37
+ end
@@ -0,0 +1,61 @@
1
+ require 'vlad'
2
+
3
+ namespace :vlad do
4
+ ##
5
+ # Mongrel app server
6
+
7
+ set :mongrel_address, "127.0.0.1"
8
+ set :mongrel_clean, false
9
+ set :mongrel_command, 'mongrel_rails'
10
+ set(:mongrel_conf) { "#{shared_path}/mongrel_cluster.conf" }
11
+ set :mongrel_config_script, nil
12
+ set :mongrel_environment, "production"
13
+ set :mongrel_group, nil
14
+ set :mongrel_log_file, nil
15
+ set :mongrel_pid_file, nil
16
+ set :mongrel_port, 8000
17
+ set :mongrel_prefix, nil
18
+ set :mongrel_servers, 2
19
+ set :mongrel_user, nil
20
+
21
+ desc "Prepares application servers for deployment. Mongrel
22
+ configuration is set via the mongrel_* variables.".cleanup
23
+
24
+ remote_task :setup_app, :roles => :app do
25
+ cmd = [
26
+ "#{mongrel_command} cluster::configure",
27
+ "-N #{mongrel_servers}",
28
+ "-p #{mongrel_port}",
29
+ "-e #{mongrel_environment}",
30
+ "-a #{mongrel_address}",
31
+ "-c #{current_path}",
32
+ "-C #{mongrel_conf}",
33
+ ("-P #{mongrel_pid_file}" if mongrel_pid_file),
34
+ ("-l #{mongrel_log_file}" if mongrel_log_file),
35
+ ("--user #{mongrel_user}" if mongrel_user),
36
+ ("--group #{mongrel_group}" if mongrel_group),
37
+ ("--prefix #{mongrel_prefix}" if mongrel_prefix),
38
+ ("-S #{mongrel_config_script}" if mongrel_config_script),
39
+ ].compact.join ' '
40
+
41
+ run cmd
42
+ end
43
+
44
+ def mongrel(cmd) # :nodoc:
45
+ cmd = "#{mongrel_command} #{cmd} -C #{mongrel_conf}"
46
+ cmd << ' --clean' if mongrel_clean
47
+ cmd
48
+ end
49
+
50
+ desc "Restart the app servers"
51
+
52
+ remote_task :start_app, :roles => :app do
53
+ run mongrel("cluster::restart")
54
+ end
55
+
56
+ desc "Stop the app servers"
57
+
58
+ remote_task :stop_app, :roles => :app do
59
+ run mongrel("cluster::stop")
60
+ end
61
+ end
@@ -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,34 @@
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
+ if revision_or_source =~ /^(\d+|head)$/i then
20
+ "#{svn_cmd} export -r #{revision_or_source} #{repository} #{destination}"
21
+ else
22
+ "#{svn_cmd} export #{revision_or_source} #{destination}"
23
+ end
24
+ end
25
+
26
+ ##
27
+ # Returns a command that maps human-friendly revision identifier +revision+
28
+ # into a subversion revision specification.
29
+
30
+ def revision(revision)
31
+ "`#{svn_cmd} info #{repository} | grep 'Revision:' | cut -f2 -d\\ `"
32
+ end
33
+ end
34
+
@@ -0,0 +1,188 @@
1
+ require 'test/vlad_test_case'
2
+ require 'vlad'
3
+
4
+ class TestRakeRemoteTask < VladTestCase
5
+ def test_enhance
6
+ util_set_hosts
7
+ body = Proc.new { 5 }
8
+ task = @vlad.remote_task(:some_task => :foo, &body)
9
+ action = Rake::RemoteTask::Action.new(task, body)
10
+ assert_equal [action], task.remote_actions
11
+ assert_equal task, action.task
12
+ assert_equal ["foo"], task.prerequisites
13
+ end
14
+
15
+ def test_enhance_with_no_task_body
16
+ util_set_hosts
17
+ util_setup_task
18
+ assert_equal [], @task.remote_actions
19
+ assert_equal [], @task.prerequisites
20
+ end
21
+
22
+ def test_execute
23
+ util_set_hosts
24
+ set :some_variable, 1
25
+ x = 5
26
+ task = @vlad.remote_task(:some_task) { x += some_variable }
27
+ task.execute nil
28
+ assert_equal 1, task.some_variable
29
+ assert_equal 2, task.remote_actions.first.workers.size
30
+ assert_equal 7, x
31
+ end
32
+
33
+ def test_execute_exposes_target_host
34
+ host "app.example.com", :app
35
+ task = remote_task(:target_task) { set(:test_target_host, target_host) }
36
+ task.execute nil
37
+ assert_equal "app.example.com", Rake::RemoteTask.fetch(:test_target_host)
38
+ end
39
+
40
+ def test_execute_with_no_hosts
41
+ @vlad.host "app.example.com", :app
42
+ t = @vlad.remote_task(:flunk, :roles => :db) { flunk "should not have run" }
43
+ e = assert_raise(Vlad::ConfigurationError) { t.execute nil }
44
+ assert_equal "No target hosts specified on task flunk for roles [:db]",
45
+ e.message
46
+ end
47
+
48
+ def test_execute_with_no_roles
49
+ t = @vlad.remote_task(:flunk, :roles => :junk) { flunk "should not have run" }
50
+ e = assert_raise(Vlad::ConfigurationError) { t.execute nil }
51
+ assert_equal "No target hosts specified on task flunk for roles [:junk]",
52
+ e.message
53
+ end
54
+
55
+ def test_execute_with_roles
56
+ util_set_hosts
57
+ set :some_variable, 1
58
+ x = 5
59
+ task = @vlad.remote_task(:some_task, :roles => :db) { x += some_variable }
60
+ task.execute nil
61
+ assert_equal 1, task.some_variable
62
+ assert_equal 6, x
63
+ end
64
+
65
+ def test_rsync
66
+ util_setup_task
67
+ @task.target_host = "app.example.com"
68
+
69
+ @task.rsync 'localfile', 'remotefile'
70
+
71
+ commands = @task.commands
72
+
73
+ assert_equal 1, commands.size, 'not enough commands'
74
+ assert_equal %w[rsync -azP --delete localfile app.example.com:remotefile],
75
+ commands.first, 'rsync'
76
+ end
77
+
78
+ def test_rsync_fail
79
+ util_setup_task
80
+ @task.target_host = "app.example.com"
81
+ @task.action = lambda { false }
82
+
83
+ e = assert_raise(Vlad::CommandFailedError) { @task.rsync 'local', 'remote' }
84
+ assert_equal "execution failed: rsync -azP --delete local app.example.com:remote", e.message
85
+ end
86
+
87
+ def test_run
88
+ util_setup_task
89
+ @task.output << "file1\nfile2\n"
90
+ @task.target_host = "app.example.com"
91
+ result = nil
92
+
93
+ out, err = util_capture do
94
+ result = @task.run("ls")
95
+ end
96
+
97
+ commands = @task.commands
98
+
99
+ assert_equal 1, commands.size, 'not enough commands'
100
+ assert_equal ["ssh", "app.example.com", "ls"],
101
+ commands.first, 'app'
102
+ assert_equal "file1\nfile2\n", result
103
+
104
+ assert_equal "file1\nfile2\n", out.read
105
+ assert_equal '', err.read
106
+ end
107
+
108
+ def test_run_failing_command
109
+ util_set_hosts
110
+ util_setup_task
111
+ @task.input = StringIO.new "file1\nfile2\n"
112
+ @task.target_host = 'app.example.com'
113
+ @task.action = lambda { 1 }
114
+
115
+ e = assert_raise(Vlad::CommandFailedError) { @task.run("ls") }
116
+ assert_equal "execution failed with status 1: ssh app.example.com ls", e.message
117
+
118
+ assert_equal 1, @task.commands.size
119
+ end
120
+
121
+ def test_run_sudo
122
+ util_setup_task
123
+ @task.output << "file1\nfile2\n"
124
+ @task.error << 'Password:'
125
+ @task.target_host = "app.example.com"
126
+ def @task.sudo_password() "my password" end # gets defined by set
127
+ result = nil
128
+
129
+ out, err = util_capture do
130
+ result = @task.run("sudo ls")
131
+ end
132
+
133
+ commands = @task.commands
134
+
135
+ assert_equal 1, commands.size, 'not enough commands'
136
+ assert_equal ['ssh', 'app.example.com', 'sudo ls'],
137
+ commands.first
138
+
139
+ assert_equal "my password\n", @task.input.string
140
+
141
+ # WARN: Technically incorrect, the password line should be
142
+ # first... this is an artifact of changes to the IO code in run
143
+ # and the fact that we have a very simplistic (non-blocking)
144
+ # testing model.
145
+ assert_equal "file1\nfile2\nPassword:\n", result
146
+
147
+ assert_equal "file1\nfile2\n", out.read
148
+ assert_equal "Password:\n", err.read
149
+ end
150
+
151
+ def test_sudo
152
+ util_setup_task
153
+ @task.target_host = "app.example.com"
154
+ @task.sudo "ls"
155
+
156
+ commands = @task.commands
157
+
158
+ assert_equal 1, commands.size, 'wrong number of commands'
159
+ assert_equal ["ssh", "app.example.com", "sudo ls"],
160
+ commands.first, 'app'
161
+ end
162
+
163
+ def util_capture
164
+ require 'stringio'
165
+ orig_stdout = $stdout.dup
166
+ orig_stderr = $stderr.dup
167
+ captured_stdout = StringIO.new
168
+ captured_stderr = StringIO.new
169
+ $stdout = captured_stdout
170
+ $stderr = captured_stderr
171
+ yield
172
+ captured_stdout.rewind
173
+ captured_stderr.rewind
174
+ return captured_stdout, captured_stderr
175
+ ensure
176
+ $stdout = orig_stdout
177
+ $stderr = orig_stderr
178
+ end
179
+
180
+ def util_setup_task(options = {})
181
+ @task = @vlad.remote_task :test_task, options
182
+ @task.commands = []
183
+ @task.output = []
184
+ @task.error = []
185
+ @task.action = nil
186
+ @task
187
+ end
188
+ end
data/test/test_vlad.rb ADDED
@@ -0,0 +1,210 @@
1
+ require 'test/vlad_test_case'
2
+ require 'vlad'
3
+
4
+ $TESTING = true
5
+
6
+ class TestVlad < VladTestCase
7
+ def test_all_hosts
8
+ util_set_hosts
9
+ assert_equal %w[app.example.com db.example.com], @vlad.all_hosts
10
+ end
11
+
12
+ def test_fetch
13
+ set :foo, 5
14
+ assert_equal 5, @vlad.fetch(:foo)
15
+ end
16
+
17
+ def test_fetch_with_default
18
+ assert_equal 5, @vlad.fetch(:not_here, 5)
19
+ end
20
+
21
+ def test_host
22
+ @vlad.host "test.example.com", :app, :db
23
+ expected = {"test.example.com" => {}}
24
+ assert_equal expected, @vlad.roles[:app]
25
+ assert_equal expected, @vlad.roles[:db]
26
+ end
27
+
28
+ def test_host_invalid
29
+ assert_raise(ArgumentError) { @vlad.host nil, :web }
30
+ end
31
+
32
+ def test_host_multiple_hosts
33
+ @vlad.host "test.example.com", :app, :db
34
+ @vlad.host "yarr.example.com", :app, :db, :no_release => true
35
+
36
+ expected = {
37
+ "test.example.com" => {},
38
+ "yarr.example.com" => {:no_release => true}
39
+ }
40
+
41
+ assert_equal expected, @vlad.roles[:app]
42
+ assert_equal expected, @vlad.roles[:db]
43
+ assert_not_equal(@vlad.roles[:db]["test.example.com"].object_id,
44
+ @vlad.roles[:app]["test.example.com"].object_id)
45
+ end
46
+
47
+ def test_hosts_for_array_of_roles
48
+ util_set_hosts
49
+ assert_equal %w[app.example.com db.example.com], @vlad.hosts_for([:app, :db])
50
+ end
51
+
52
+ def test_hosts_for_one_role
53
+ util_set_hosts
54
+ @vlad.host "app2.example.com", :app
55
+ assert_equal %w[app.example.com app2.example.com], @vlad.hosts_for(:app)
56
+ end
57
+
58
+ def test_hosts_for_multiple_roles
59
+ util_set_hosts
60
+ assert_equal %w[app.example.com db.example.com], @vlad.hosts_for(:app, :db)
61
+ end
62
+
63
+ def test_hosts_for_unique
64
+ util_set_hosts
65
+ @vlad.host "app.example.com", :web
66
+ assert_equal %w[app.example.com db.example.com], @vlad.hosts_for(:app, :db, :web)
67
+ end
68
+
69
+ def test_initialize
70
+ @vlad.set_defaults # ensure these three are virginal
71
+ assert_raise(Vlad::ConfigurationError) { @vlad.repository }
72
+ assert_raise(Vlad::ConfigurationError) { @vlad.deploy_to }
73
+ assert_raise(Vlad::ConfigurationError) { @vlad.domain }
74
+ end
75
+
76
+ def test_role
77
+ @vlad.role :app, "test.example.com"
78
+ expected = {"test.example.com" => {}}
79
+ assert_equal expected, @vlad.roles[:app]
80
+ end
81
+
82
+ def test_role_multiple_hosts
83
+ @vlad.role :app, "test.example.com"
84
+ @vlad.role :app, "yarr.example.com", :no_release => true
85
+ expected = {
86
+ "test.example.com" => {},
87
+ "yarr.example.com" => {:no_release => true}
88
+ }
89
+ assert_equal expected, @vlad.roles[:app]
90
+ end
91
+
92
+ def test_role_multiple_roles
93
+ @vlad.role :app, "test.example.com", :primary => true
94
+ @vlad.role :db, "yarr.example.com", :no_release => true
95
+ expected_db = { "yarr.example.com" => {:no_release => true} }
96
+ assert_equal expected_db, @vlad.roles[:db]
97
+ expected_app = { "test.example.com" => {:primary => true} }
98
+ assert_equal expected_app, @vlad.roles[:app]
99
+ end
100
+
101
+ def test_remote_task
102
+ t = @vlad.remote_task(:test_task) { 5 }
103
+ assert_equal @task_count + 1, Rake.application.tasks.size
104
+ assert_equal({ :roles => [] }, t.options)
105
+ end
106
+
107
+ def test_remote_task_all_hosts_by_default
108
+ util_set_hosts
109
+ t = @vlad.remote_task(:test_task) { 5 }
110
+ assert_equal %w[app.example.com db.example.com], t.target_hosts
111
+ end
112
+
113
+ def test_remote_task_environment_override
114
+ old_env_hosts = ENV["HOSTS"]
115
+ ENV["HOSTS"] = 'other1.example.com, other2.example.com'
116
+ util_set_hosts
117
+ t = @vlad.remote_task(:test_task) { 5 }
118
+ assert_equal %w[other1.example.com other2.example.com], t.target_hosts
119
+ ensure
120
+ ENV["HOSTS"] = old_env_hosts
121
+ end
122
+
123
+ def test_remote_task_body_set
124
+ set(:some_variable, 5)
125
+ @vlad.host 'www.example.com', :app
126
+ @vlad.remote_task(:some_task) do $some_task_result = some_variable end
127
+
128
+ Rake::Task['some_task'].execute nil
129
+ assert_equal @vlad.fetch(:some_variable), $some_task_result
130
+ end
131
+
132
+ def test_remote_task_with_options
133
+ t = @vlad.remote_task :test_task, :roles => [:app, :db] do
134
+ fail "should not run"
135
+ end
136
+ assert_equal({:roles => [:app, :db]}, t.options)
137
+ end
138
+
139
+ def test_remote_task_before_host_declaration
140
+ t = @vlad.remote_task :test_task, :roles => :web do 5 end
141
+ @vlad.host 'www.example.com', :web
142
+ assert_equal %w[www.example.com], t.target_hosts
143
+ end
144
+
145
+ def test_remote_task_role_override
146
+ host "db1", :db
147
+ host "db2", :db
148
+ host "db3", :db
149
+ host "master", :master_db
150
+
151
+ remote_task(:migrate_the_db, :roles => [:db]) { flunk "bad!" }
152
+ task = Rake::Task["migrate_the_db"]
153
+ assert_equal %w[db1 db2 db3], task.target_hosts
154
+
155
+ task.options[:roles] = :master_db
156
+ assert_equal %w[master], task.target_hosts
157
+
158
+ task.options[:roles] = [:master_db]
159
+ assert_equal %w[master], task.target_hosts
160
+ end
161
+
162
+ def test_set
163
+ set :test, 5
164
+ assert_equal 5, @vlad.test
165
+ end
166
+
167
+ def test_set_lazy_block_evaluation
168
+ set(:test) { fail "lose" }
169
+ assert_raise(RuntimeError) { @vlad.test }
170
+ end
171
+
172
+ def test_set_with_block
173
+ x = 1
174
+ set(:test) { x += 2 }
175
+
176
+ assert_equal 3, @vlad.test
177
+ assert_equal 3, @vlad.test
178
+ end
179
+
180
+ def test_set_with_reference
181
+ @vlad.instance_eval do
182
+ set(:var_one) { var_two }
183
+ set(:var_two) { var_three }
184
+ set(:var_three) { 5 }
185
+ end
186
+
187
+ assert_equal 5, @vlad.var_one
188
+ end
189
+
190
+ def test_set_with_block_and_value
191
+ e = assert_raise(ArgumentError) do
192
+ set(:test, 5) { 6 }
193
+ end
194
+ assert_equal "cannot provide both a value and a block", e.message
195
+ end
196
+
197
+ def test_set_with_nil
198
+ set(:test, nil)
199
+ assert_equal nil, @vlad.test
200
+ end
201
+
202
+ def test_set_with_reserved_name
203
+ $TESTING = false
204
+ e = assert_raise(ArgumentError) { set(:all_hosts, []) }
205
+ assert_equal "cannot set reserved name: 'all_hosts'", e.message
206
+ ensure
207
+ $TESTING = true
208
+ end
209
+ end
210
+