kaykay-vlad 1.2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,186 @@
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 for task: flunk", e.message
45
+ end
46
+
47
+ def test_execute_with_no_roles
48
+ t = @vlad.remote_task(:flunk, :roles => :junk) { flunk "should not have run" }
49
+ e = assert_raise(Vlad::ConfigurationError) { t.execute nil }
50
+ assert_equal "No target hosts specified for task: flunk", e.message
51
+ end
52
+
53
+ def test_execute_with_roles
54
+ util_set_hosts
55
+ set :some_variable, 1
56
+ x = 5
57
+ task = @vlad.remote_task(:some_task, :roles => :db) { x += some_variable }
58
+ task.execute nil
59
+ assert_equal 1, task.some_variable
60
+ assert_equal 6, x
61
+ end
62
+
63
+ def test_rsync
64
+ util_setup_task
65
+ @task.target_host = "app.example.com"
66
+
67
+ @task.rsync 'localfile', 'remotefile'
68
+
69
+ commands = @task.commands
70
+
71
+ assert_equal 1, commands.size, 'not enough commands'
72
+ assert_equal %w[rsync -azP --delete localfile app.example.com:remotefile],
73
+ commands.first, 'rsync'
74
+ end
75
+
76
+ def test_rsync_fail
77
+ util_setup_task
78
+ @task.target_host = "app.example.com"
79
+ @task.action = lambda { false }
80
+
81
+ e = assert_raise(Vlad::CommandFailedError) { @task.rsync 'local', 'remote' }
82
+ assert_equal "execution failed: rsync -azP --delete local app.example.com:remote", e.message
83
+ end
84
+
85
+ def test_run
86
+ util_setup_task
87
+ @task.output << "file1\nfile2\n"
88
+ @task.target_host = "app.example.com"
89
+ result = nil
90
+
91
+ out, err = util_capture do
92
+ result = @task.run("ls")
93
+ end
94
+
95
+ commands = @task.commands
96
+
97
+ assert_equal 1, commands.size, 'not enough commands'
98
+ assert_equal ["ssh", "app.example.com", "ls"],
99
+ commands.first, 'app'
100
+ assert_equal "file1\nfile2\n", result
101
+
102
+ assert_equal "file1\nfile2\n", out.read
103
+ assert_equal '', err.read
104
+ end
105
+
106
+ def test_run_failing_command
107
+ util_set_hosts
108
+ util_setup_task
109
+ @task.input = StringIO.new "file1\nfile2\n"
110
+ @task.target_host = 'app.example.com'
111
+ @task.action = lambda { 1 }
112
+
113
+ e = assert_raise(Vlad::CommandFailedError) { @task.run("ls") }
114
+ assert_equal "execution failed with status 1: ssh app.example.com ls", e.message
115
+
116
+ assert_equal 1, @task.commands.size
117
+ end
118
+
119
+ def test_run_sudo
120
+ util_setup_task
121
+ @task.output << "file1\nfile2\n"
122
+ @task.error << 'Password:'
123
+ @task.target_host = "app.example.com"
124
+ def @task.sudo_password() "my password" end # gets defined by set
125
+ result = nil
126
+
127
+ out, err = util_capture do
128
+ result = @task.run("sudo ls")
129
+ end
130
+
131
+ commands = @task.commands
132
+
133
+ assert_equal 1, commands.size, 'not enough commands'
134
+ assert_equal ['ssh', 'app.example.com', 'sudo ls'],
135
+ commands.first
136
+
137
+ assert_equal "my password\n", @task.input.string
138
+
139
+ # WARN: Technically incorrect, the password line should be
140
+ # first... this is an artifact of changes to the IO code in run
141
+ # and the fact that we have a very simplistic (non-blocking)
142
+ # testing model.
143
+ assert_equal "file1\nfile2\nPassword:\n", result
144
+
145
+ assert_equal "file1\nfile2\n", out.read
146
+ assert_equal "Password:\n", err.read
147
+ end
148
+
149
+ def test_sudo
150
+ util_setup_task
151
+ @task.target_host = "app.example.com"
152
+ @task.sudo "ls"
153
+
154
+ commands = @task.commands
155
+
156
+ assert_equal 1, commands.size, 'wrong number of commands'
157
+ assert_equal ["ssh", "app.example.com", "sudo ls"],
158
+ commands.first, 'app'
159
+ end
160
+
161
+ def util_capture
162
+ require 'stringio'
163
+ orig_stdout = $stdout.dup
164
+ orig_stderr = $stderr.dup
165
+ captured_stdout = StringIO.new
166
+ captured_stderr = StringIO.new
167
+ $stdout = captured_stdout
168
+ $stderr = captured_stderr
169
+ yield
170
+ captured_stdout.rewind
171
+ captured_stderr.rewind
172
+ return captured_stdout, captured_stderr
173
+ ensure
174
+ $stdout = orig_stdout
175
+ $stderr = orig_stderr
176
+ end
177
+
178
+ def util_setup_task(options = {})
179
+ @task = @vlad.remote_task :test_task, options
180
+ @task.commands = []
181
+ @task.output = []
182
+ @task.error = []
183
+ @task.action = nil
184
+ @task
185
+ end
186
+ end
@@ -0,0 +1,211 @@
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 "", :app, :db }
30
+ assert_raise(ArgumentError) { @vlad.host nil, :web }
31
+ end
32
+
33
+ def test_host_multiple_hosts
34
+ @vlad.host "test.example.com", :app, :db
35
+ @vlad.host "yarr.example.com", :app, :db, :no_release => true
36
+
37
+ expected = {
38
+ "test.example.com" => {},
39
+ "yarr.example.com" => {:no_release => true}
40
+ }
41
+
42
+ assert_equal expected, @vlad.roles[:app]
43
+ assert_equal expected, @vlad.roles[:db]
44
+ assert_not_equal(@vlad.roles[:db]["test.example.com"].object_id,
45
+ @vlad.roles[:app]["test.example.com"].object_id)
46
+ end
47
+
48
+ def test_hosts_for_array_of_roles
49
+ util_set_hosts
50
+ assert_equal %w[app.example.com db.example.com], @vlad.hosts_for([:app, :db])
51
+ end
52
+
53
+ def test_hosts_for_one_role
54
+ util_set_hosts
55
+ @vlad.host "app2.example.com", :app
56
+ assert_equal %w[app.example.com app2.example.com], @vlad.hosts_for(:app)
57
+ end
58
+
59
+ def test_hosts_for_multiple_roles
60
+ util_set_hosts
61
+ assert_equal %w[app.example.com db.example.com], @vlad.hosts_for(:app, :db)
62
+ end
63
+
64
+ def test_hosts_for_unique
65
+ util_set_hosts
66
+ @vlad.host "app.example.com", :web
67
+ assert_equal %w[app.example.com db.example.com], @vlad.hosts_for(:app, :db, :web)
68
+ end
69
+
70
+ def test_initialize
71
+ @vlad.set_defaults # ensure these three are virginal
72
+ assert_raise(Vlad::ConfigurationError) { @vlad.repository }
73
+ assert_raise(Vlad::ConfigurationError) { @vlad.deploy_to }
74
+ assert_raise(Vlad::ConfigurationError) { @vlad.domain }
75
+ end
76
+
77
+ def test_role
78
+ @vlad.role :app, "test.example.com"
79
+ expected = {"test.example.com" => {}}
80
+ assert_equal expected, @vlad.roles[:app]
81
+ end
82
+
83
+ def test_role_multiple_hosts
84
+ @vlad.role :app, "test.example.com"
85
+ @vlad.role :app, "yarr.example.com", :no_release => true
86
+ expected = {
87
+ "test.example.com" => {},
88
+ "yarr.example.com" => {:no_release => true}
89
+ }
90
+ assert_equal expected, @vlad.roles[:app]
91
+ end
92
+
93
+ def test_role_multiple_roles
94
+ @vlad.role :app, "test.example.com", :primary => true
95
+ @vlad.role :db, "yarr.example.com", :no_release => true
96
+ expected_db = { "yarr.example.com" => {:no_release => true} }
97
+ assert_equal expected_db, @vlad.roles[:db]
98
+ expected_app = { "test.example.com" => {:primary => true} }
99
+ assert_equal expected_app, @vlad.roles[:app]
100
+ end
101
+
102
+ def test_remote_task
103
+ t = @vlad.remote_task(:test_task) { 5 }
104
+ assert_equal @task_count + 1, Rake.application.tasks.size
105
+ assert_equal Hash.new, t.options
106
+ end
107
+
108
+ def test_remote_task_all_hosts_by_default
109
+ util_set_hosts
110
+ t = @vlad.remote_task(:test_task) { 5 }
111
+ assert_equal %w[app.example.com db.example.com], t.target_hosts
112
+ end
113
+
114
+ def test_remote_task_environment_override
115
+ old_env_hosts = ENV["HOSTS"]
116
+ ENV["HOSTS"] = 'other1.example.com, other2.example.com'
117
+ util_set_hosts
118
+ t = @vlad.remote_task(:test_task) { 5 }
119
+ assert_equal %w[other1.example.com other2.example.com], t.target_hosts
120
+ ensure
121
+ ENV["HOSTS"] = old_env_hosts
122
+ end
123
+
124
+ def test_remote_task_body_set
125
+ set(:some_variable, 5)
126
+ @vlad.host 'www.example.com', :app
127
+ @vlad.remote_task(:some_task) do $some_task_result = some_variable end
128
+
129
+ Rake::Task['some_task'].execute nil
130
+ assert_equal @vlad.fetch(:some_variable), $some_task_result
131
+ end
132
+
133
+ def test_remote_task_with_options
134
+ t = @vlad.remote_task :test_task, :roles => [:app, :db] do
135
+ fail "should not run"
136
+ end
137
+ assert_equal({:roles => [:app, :db]}, t.options)
138
+ end
139
+
140
+ def test_remote_task_before_host_declaration
141
+ t = @vlad.remote_task :test_task, :roles => :web do 5 end
142
+ @vlad.host 'www.example.com', :web
143
+ assert_equal %w[www.example.com], t.target_hosts
144
+ end
145
+
146
+ def test_remote_task_role_override
147
+ host "db1", :db
148
+ host "db2", :db
149
+ host "db3", :db
150
+ host "master", :master_db
151
+
152
+ remote_task(:migrate_the_db, :roles => [:db]) { flunk "bad!" }
153
+ task = Rake::Task["migrate_the_db"]
154
+ assert_equal %w[db1 db2 db3], task.target_hosts
155
+
156
+ task.options[:roles] = :master_db
157
+ assert_equal %w[master], task.target_hosts
158
+
159
+ task.options[:roles] = [:master_db]
160
+ assert_equal %w[master], task.target_hosts
161
+ end
162
+
163
+ def test_set
164
+ set :test, 5
165
+ assert_equal 5, @vlad.test
166
+ end
167
+
168
+ def test_set_lazy_block_evaluation
169
+ set(:test) { fail "lose" }
170
+ assert_raise(RuntimeError) { @vlad.test }
171
+ end
172
+
173
+ def test_set_with_block
174
+ x = 1
175
+ set(:test) { x += 2 }
176
+
177
+ assert_equal 3, @vlad.test
178
+ assert_equal 3, @vlad.test
179
+ end
180
+
181
+ def test_set_with_reference
182
+ @vlad.instance_eval do
183
+ set(:var_one) { var_two }
184
+ set(:var_two) { var_three }
185
+ set(:var_three) { 5 }
186
+ end
187
+
188
+ assert_equal 5, @vlad.var_one
189
+ end
190
+
191
+ def test_set_with_block_and_value
192
+ e = assert_raise(ArgumentError) do
193
+ set(:test, 5) { 6 }
194
+ end
195
+ assert_equal "cannot provide both a value and a block", e.message
196
+ end
197
+
198
+ def test_set_with_nil
199
+ set(:test, nil)
200
+ assert_equal nil, @vlad.test
201
+ end
202
+
203
+ def test_set_with_reserved_name
204
+ $TESTING = false
205
+ e = assert_raise(ArgumentError) { set(:all_hosts, []) }
206
+ assert_equal "cannot set reserved name: 'all_hosts'", e.message
207
+ ensure
208
+ $TESTING = true
209
+ end
210
+ end
211
+