gabrielg-vlad 1.2.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.
@@ -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
+