siefca-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,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
+