tdd_deploy 0.0.3 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/bin/tdd_deploy_site_installer +7 -0
  2. data/lib/tasks/tdd_deploy_site_install.rake +17 -0
  3. data/lib/tdd_deploy/assertions.rb +114 -77
  4. data/lib/tdd_deploy/base.rb +1 -1
  5. data/lib/tdd_deploy/configurator.rb +78 -0
  6. data/lib/tdd_deploy/copy_methods.rb +54 -0
  7. data/lib/tdd_deploy/deploy_test_methods.rb +20 -22
  8. data/lib/tdd_deploy/environ.rb +12 -6
  9. data/lib/tdd_deploy/host_tests/host_connection.rb +3 -4
  10. data/lib/tdd_deploy/host_tests/remote_ip_tables.rb +21 -13
  11. data/lib/tdd_deploy/host_tests/remote_monit.rb +2 -6
  12. data/lib/tdd_deploy/host_tests/remote_nginx.rb +2 -6
  13. data/lib/tdd_deploy/host_tests/remote_postfix.rb +3 -7
  14. data/lib/tdd_deploy/host_tests/remote_postgresql.rb +2 -6
  15. data/lib/tdd_deploy/run_methods.rb +25 -18
  16. data/lib/tdd_deploy/server.rb +92 -12
  17. data/lib/tdd_deploy/site_tests/site_database.rb +1 -1
  18. data/lib/tdd_deploy/site_tests/site_layout.rb +24 -20
  19. data/lib/tdd_deploy/site_tests/site_user.rb +1 -1
  20. data/lib/tdd_deploy/version.rb +1 -1
  21. data/tests/test_assertions.rb +59 -14
  22. data/tests/test_configurator.rb +34 -0
  23. data/tests/test_copy_methods.rb +106 -0
  24. data/tests/{test_test_deploy_methods.rb → test_deploy_test_methods.rb} +23 -11
  25. data/tests/test_environ.rb +12 -7
  26. data/tests/test_remote_ip_tables.rb +11 -4
  27. data/tests/test_run_methods.rb +32 -15
  28. data/tests/test_server.rb +6 -9
  29. data/tests/test_site_database.rb +21 -0
  30. data/tests/test_site_layout.rb +20 -5
  31. data/tests/test_tdd_deploy_context.rb +16 -0
  32. data/tests/test_tdd_deploy_server.rb +3 -3
  33. metadata +20 -13
  34. data/tests/test_colored_tests.rb +0 -47
@@ -0,0 +1,7 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'tdd_deploy/site_installer'
6
+
7
+ TddDeploy::SiteInstaller.new.install
@@ -0,0 +1,17 @@
1
+ require 'rake'
2
+
3
+ desc 'Clean'
4
+ task :clean do
5
+ ['monitrc', 'one_thin_server'].each do |fname|
6
+ FileUtils.rm File.join('site', fname)
7
+ end
8
+ ['nginx.conf', 'thin.conf', 'thin_one_server.conf'].each do |fname|
9
+ FileUtils.rm File.join('config', fname)
10
+ end
11
+ end
12
+
13
+ desc 'Create Site Config & Site files'
14
+ task :create_site_files do
15
+ require 'tdd_deploy_site_installer'
16
+
17
+ end
@@ -1,14 +1,16 @@
1
1
  module TddDeploy
2
2
  module Assertions
3
- GREEN = '#0c0'
4
- RED = '#c00'
5
- WRAP_ELT_TAG = 'p'
3
+ GREEN = '#080'
4
+ RED = '#800'
5
+ GROUP_ELT_TAG = 'ul'
6
+ HEADER_ELT_TAG = 'h2'
7
+ RESULT_ELT_TAG = 'li'
6
8
  # TddDeploy re-implements popular assertions so that they can be used
7
9
  # in multi-host testing.
8
10
  #
9
11
  # These assertions differ from usual TDD assertions in that they do not stop
10
12
  # tests after failing. Rather they continue and accumulate failure counts and messages
11
- # which can be displayed using *announce_test_results()*.
13
+ # which can be displayed using *announce_formatted_test_results()*.
12
14
  #
13
15
  # all assertions return boolean *true* or *false*
14
16
 
@@ -21,126 +23,161 @@ module TddDeploy
21
23
  # are initialized - which can easily differ from where they are declared.
22
24
  class Stats
23
25
  class << self
24
- attr_accessor :test_count, :failure_count, :failure_messages, :test_messages
26
+ attr_accessor :test_results
25
27
  end
26
28
  end
27
29
 
28
- # test_results returns the string string of all test messages
29
- def test_results
30
- unless Stats.failure_count.nil? || Stats.failure_count == 0
31
- str = "<#{WRAP_ELT_TAG} style=\"color:#{RED}\">#{Stats.failure_count} Failed Test" + (Stats.failure_count == 1 ? '' : 's') + "</#{WRAP_ELT_TAG}>"
32
- else
33
- str = "<#{WRAP_ELT_TAG} style=\"color:#{GREEN}\">All Tests Passed</#{WRAP_ELT_TAG}>"
34
- end
35
- Stats.test_messages ? str + Stats.test_messages.join("\n") : str
36
- end
37
-
38
- def test_failures
39
- return '' unless Stats.failure_count > 0
40
- "<#{WRAP_ELT_TAG} style=\"color:#{RED}\">Failed #{Stats.failure_count} tests</#{WRAP_ELT_TAG}>\n" + Stats.failure_messages.join("\n")
41
- end
42
-
43
- def test_messages
44
- Stats.test_messages
45
- end
46
-
47
- # reset_tests zeros out failure messages and count
48
- def reset_tests
49
- Stats.test_count = 0
50
- Stats.failure_count = 0
51
- Stats.failure_messages = []
52
- Stats.test_messages = []
53
- end
54
-
55
30
  # Assertions all return true or false. The last parameter is always the assertions
56
31
  # message and is optional.
57
32
  #
58
33
  # assert(prediccate, msg) returns true if prediccate is true, else adds *msg*
59
34
  # to failure messages and returns false
60
- def assert predicate, msg
61
- assert_primative predicate, msg
35
+ def assert key, predicate, msg
36
+ assert_primative key, predicate, msg
62
37
  end
63
38
 
64
- def assert_equal expect, value, msg
65
- assert_primative expect == value, msg
39
+ def assert_equal key, expect, value, msg
40
+ assert_primative key, expect == value, msg
66
41
  end
67
42
 
68
- def assert_match regx, value, msg
43
+ def assert_match key, regx, value, msg
69
44
  regx = Regexp.new(regx.to_s) unless regx.instance_of? Regexp
70
- assert_primative regx.match(value), msg
45
+ assert_primative key, regx.match(value), msg
71
46
  end
72
47
 
73
- def assert_nil value, msg
74
- assert_primative value.nil?, msg
48
+ def assert_nil key, value, msg
49
+ assert_primative key, value.nil?, msg
75
50
  end
76
51
 
77
- def assert_not_nil value, msg
78
- assert_primative !value.nil?, msg
52
+ def assert_not_nil key, value, msg
53
+ assert_primative key, !value.nil?, msg
79
54
  end
80
55
 
81
- def assert_raises exception = Exception, msg, &block
56
+ def assert_raises key, exception = Exception, msg, &block
82
57
  begin
83
58
  block.call
84
59
  rescue exception => e
60
+ pass key, msg
85
61
  return true
86
62
  end
87
- assert_primative false, msg
63
+ fail key, msg
88
64
  end
89
65
 
90
- def refute predicate, msg
91
- assert_primative !predicate, msg
66
+ def refute key, predicate, msg
67
+ assert_primative key, !predicate, msg
92
68
  end
93
69
 
94
- def refute_equal expect, value, msg
95
- assert_primative expect != value, msg
70
+ def refute_equal key, expect, value, msg
71
+ assert_primative key, expect != value, msg
96
72
  end
97
73
 
98
- def pass msg
99
- assert_primative true, msg
74
+ def pass key, msg
75
+ assert_primative key, true, msg
76
+ end
77
+
78
+ def fail key, msg
79
+ assert_primative key, false, msg
80
+ end
81
+
82
+ # public stats access
83
+
84
+ # don't use formatted_test_results or formatted_test_results_for_key
85
+ # use the supplied test_results.html.erb template instead
86
+ # formatted_test_results returns the string string of all test messages
87
+ def formatted_test_results
88
+ str = ''
89
+ Stats.test_results.keys.sort.each do |key|
90
+ str += formatted_test_results_for_key(key)
91
+ end
92
+ str
93
+ end
94
+
95
+ def formatted_test_results_for_key key
96
+ str = "<#{GROUP_ELT_TAG} class=\"test-result-group\" id=\"test-result-group-#{key}\">\n<#{HEADER_ELT_TAG} class=\"test-result-header\" id=\"test-result-header-#{key}\">Results for '#{key}'</#{HEADER_ELT_TAG}>\n"
97
+ if failure_count(key) == 0
98
+ str += "<#{RESULT_ELT_TAG} class=\"test-result-summary-success\" id=\"test-result-summary-#{key}\">All #{test_count(key)} Tests Passed</#{RESULT_ELT_TAG}>\n"
99
+ else
100
+ str += "<#{RESULT_ELT_TAG} class=\"test-result-summary-failure\" id=\"test-result-summary-#{key}\">#{failure_count(key)} of #{test_count(key)} Tests Failed</#{RESULT_ELT_TAG}>\n"
101
+ end
102
+ toggle = true
103
+ tmp = Stats.test_results[key].map { |msg| toggle = !toggle ; "<#{RESULT_ELT_TAG} class=\"#{(toggle ? "even" : "odd")}\">#{msg}</#{RESULT_ELT_TAG}>\n" }
104
+ str += tmp.join("\n") + "\n" if Stats.test_results
105
+ str + "</#{GROUP_ELT_TAG}>\n"
106
+ end
107
+
108
+ # test_results returns the test_results hash
109
+ def test_results
110
+ Stats.test_results
111
+ end
112
+
113
+ # reset_tests
114
+ def reset_tests
115
+ Stats.test_results = {}
116
+ end
117
+
118
+ # removes all failed test results
119
+ def remove_failed_tests
120
+ tmp = {}
121
+ Stats.test_results.each do |host, results|
122
+ tmp[host] = results.select { |tmp| tmp[0] }
123
+ end
124
+ Stats.test_results = tmp
125
+ end
126
+
127
+ def total_failures
128
+ count = 0
129
+ Stats.test_results.values.each do |messages|
130
+ count += messages.select { |msg| !msg[0] }.length
131
+ end
132
+ count
133
+ end
134
+
135
+ def total_tests
136
+ Stats.test_results.values.reduce(0) { |memo, obj| memo += obj.length }
100
137
  end
101
138
 
102
- def fail msg
103
- assert_primative false, msg
139
+ def failure_count(key)
140
+ return nil unless Stats.test_results[key]
141
+ failure_messages(key).length
142
+ end
143
+
144
+ def test_count(key)
145
+ return nil unless Stats.test_results[key]
146
+ Stats.test_results[key].length
147
+ end
148
+
149
+ def test_messages(key)
150
+ Stats.test_results[key]
151
+ end
152
+
153
+ def failure_messages(key)
154
+ Stats.test_results[key].select { |tmp| !tmp[0] }
104
155
  end
105
156
 
106
157
  # private methods
107
158
  private
108
- def assert_primative predicate, msg
109
- predicate ? test_passed("Passed: #{msg}") : test_failed("Failed: #{msg}")
159
+ def assert_primative key, predicate, msg
160
+ predicate ? test_passed(key, "Passed: #{msg}") : test_failed(key, "Failed: #{msg}")
110
161
  predicate
111
162
  end
112
163
 
113
164
  # test message handling
114
- def test_failed(msg)
115
- msg = "<#{WRAP_ELT_TAG} style=\"color:#{RED}\">#{msg}</#{WRAP_ELT_TAG}>"
116
- add_failure(msg)
117
- add_message(msg)
165
+ def test_failed(key, msg)
166
+ add_message(key, false, msg)
118
167
  end
119
168
 
120
- def test_passed(msg)
121
- msg = "<#{WRAP_ELT_TAG} style=\"color:#{GREEN}\">#{msg}</#{WRAP_ELT_TAG}>"
122
- add_message(msg)
123
- end
124
-
125
- def add_failure(msg)
126
- Stats.failure_messages ||= []
127
- Stats.failure_count ||= 0
128
- Stats.failure_messages.push(msg)
129
- Stats.failure_count += 1
169
+ def test_passed(key, msg)
170
+ add_message(key, true, msg)
130
171
  end
131
172
 
132
- def add_message(msg)
133
- Stats.test_messages ||= []
134
- Stats.test_count ||= 0
135
- Stats.test_messages.push(msg)
136
- Stats.test_count += 1
173
+ def add_message(key, result, msg)
174
+ Stats.test_results ||= {}
175
+ Stats.test_results[key] ||= []
176
+ Stats.test_results[key].push([result, msg])
137
177
  end
138
178
 
139
179
  def self.included(mod)
140
- Stats.test_count = 0
141
- Stats.failure_count = 0
142
- Stats.failure_messages = []
143
- Stats.test_messages = []
180
+ Stats.test_results = {}
144
181
  end
145
182
  end
146
183
  end
@@ -25,7 +25,7 @@ require 'tdd_deploy/deploy_test_methods'
25
25
  #
26
26
  # class HostFacilityTest < TddDeploy::Base
27
27
  # def test_for_file
28
- # deploy_test_on_all_hosts_as user_id, match_string_or_regx, err_msg { command }
28
+ # deploy_test_on_hosts_as user_id, match_string_or_regx, err_msg { command }
29
29
  # end
30
30
  # etc
31
31
  # end
@@ -0,0 +1,78 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- ruby-mode -*-
3
+
4
+ require 'erb'
5
+ require 'tdd_deploy/base'
6
+
7
+ module TddDeploy
8
+ # == TddDeployConfigurator
9
+ #
10
+ # TddDeployConfigurator is used to create site/host specific configuration files for
11
+ # sites. The files are defined by templates in subdirectories of the 'site-erb' directory.
12
+ # At present there are templates in 'site-erb/config' and 'site-erb/site'. Rendered files
13
+ # are written to corresponding subdirectories of the app. For example, 'site-erb/config/foo.erb'
14
+ # will produce the file 'app/config/foo'
15
+ #
16
+ # files dropped into 'app/site/' are assumed to be executable, so their permissions are
17
+ # set to 0755
18
+ class Configurator < TddDeploy::Base
19
+ # install - reads all the templates in gem-home/site-erb, renders them using the
20
+ # current environment context, and writes the renderings to the appropriate
21
+ # files in app/sites and app/config
22
+ def make_configuration_files
23
+ # create local directory for output files
24
+ tdd_deploy_configs = File.join Dir.pwd, 'tdd_deploy_configs'
25
+ Dir.mkdir(tdd_deploy_configs) unless File.exists? tdd_deploy_configs
26
+
27
+ ['balance_hosts', 'db_hosts', 'web_hosts'].each do |host_dir|
28
+ host_path = File.join(tdd_deploy_configs, host_dir)
29
+ Dir.mkdir(host_path) unless File.exists? host_path
30
+
31
+ ['config', 'site'].each do |subdir|
32
+ subdir_path = File.join(host_path, subdir)
33
+ Dir.mkdir(subdir_path) unless File.exists? subdir_path
34
+ end
35
+ end
36
+
37
+ # instantiate all templates and write output to tdd_deploy_configs
38
+ erb_dir = File.expand_path('../site-erb', __FILE__)
39
+ Dir.new(erb_dir).each do |host_dir_fname|
40
+ next if host_dir_fname[0] == '.'
41
+
42
+ host_dir_path = File.join(erb_dir, host_dir_fname)
43
+
44
+ Dir.new(host_dir_path).each do |subdir|
45
+ next if subdir[0] == '.'
46
+
47
+ subdir_path = File.join(host_dir_path, subdir)
48
+
49
+ Dir.new(subdir_path).each do |fname|
50
+ file_path = File.join(subdir_path, fname)
51
+ next unless fname =~ /\.erb$/ && File.exists?(file_path)
52
+
53
+ f = File.new(file_path)
54
+ # '>' removes new-lines from lines ending in %>
55
+ # template = ERB.new f.read, nil, '>'
56
+ # '>' removes new-lines from lines starting with <% and ending in %>
57
+ template = ERB.new f.read, nil, '<>'
58
+ f.close
59
+
60
+ file_content = template.result(binding)
61
+
62
+ out_fname = File.basename(fname, '.erb')
63
+
64
+ Dir.mkdir(subdir_path) unless File.exists? subdir
65
+ out_path = File.join(tdd_deploy_configs, host_dir_fname, subdir, out_fname)
66
+
67
+ f = File.new(out_path, "w")
68
+ f.write template.result(binding)
69
+ f.close
70
+
71
+ # make files in 'app/site' executable
72
+ File.chmod 0755, out_path if subdir == 'site'
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,54 @@
1
+ require 'net/ssh'
2
+ require 'net/sftp'
3
+
4
+ module TddDeploy
5
+ module RunMethods
6
+
7
+ def copy_string_to_remote_file_on_hosts_as userid, host_list, str, dst
8
+ result = true
9
+ host_list = [host_list] if host_list.is_a? String
10
+ host_list.uniq.each do |host|
11
+ result &= copy_string_to_remote_file_as userid, host, str, dst
12
+ end
13
+ result
14
+ end
15
+
16
+ def copy_file_to_remote_on_hosts_as userid, host_list, src, dst
17
+ result = true
18
+ host_list = [host_list] if host_list.is_a? String
19
+ host_list.uniq.each do |host|
20
+ result &= copy_file_to_remote_as userid, host, src, dst
21
+ end
22
+ result
23
+ end
24
+
25
+ #single host methods
26
+
27
+ def mkdir_on_remote_as userid, host, dir, options = {}
28
+ result = nil
29
+ options[:permissions] = 0755 unless options.include? :permissions
30
+ Net::SFTP.start(host, userid) do |sftp|
31
+ result = sftp.mkdir dir, options
32
+ end
33
+ result
34
+ end
35
+
36
+ def copy_string_to_remote_file_as userid, host, str, dst
37
+ result = nil
38
+ Net::SFTP.start(host, userid) do |sftp|
39
+ result = sftp.file.open(dst, "w") do |f|
40
+ f.write str
41
+ end
42
+ end
43
+ result
44
+ end
45
+
46
+ def copy_file_to_remote_as(userid, host, src, dst)
47
+ require 'net/sftp'
48
+ raise ArgumentError.new("file name cannot be empty") if src.empty?
49
+ raise RuntimeError.new("unable to copy #{src} to #{userid}@#{host}: #{src} not found") unless File.exists? src
50
+
51
+ copy_string_to_remote_file_as userid, host, File.new(src).read, dst
52
+ end
53
+ end
54
+ end
@@ -6,36 +6,34 @@ module TddDeploy
6
6
  include TddDeploy::Assertions
7
7
  include TddDeploy::RunMethods
8
8
 
9
- # deploy_test_on_all_hosts runs the command(s) return by '&block' on all hosts in self.hosts
10
- # as user 'self.host_admin'.
11
- # For each host, an error is declared if EITHER STDOUT does not match 'match_expr_or_str'
12
- # OR if the command returns anything on STDERR.
13
- # 'match_expr_or_str' can be a Regexp or a string (which will be converted to a Regexp)
14
- def deploy_test_on_all_hosts(match_expr_or_str, success_msg, &block)
15
- deploy_test_on_all_hosts_as self.host_admin, match_expr_or_str, success_msg, &block
9
+ def deploy_test_process_running_on_hosts_as(userid, host_list, pid_file_path, success_msg = nil)
10
+ success_msg ||= "Process associated with #{pid_file_path} should be running"
11
+ ret = deploy_test_file_exists_on_hosts_as(userid, host_list, pid_file_path, success_msg + " no such pid file: #{pid_file_path}") ||
12
+ ret &= deploy_test_on_hosts_as(userid, host_list, /.+\n\s*\d+.*?\d\d:\d\d:\d\d/, "Process for #{pid_file_path} is running") do
13
+ "ps -p `cat #{pid_file_path} | awk '{ print $1 ; exit }'`"
14
+ end
16
15
  end
17
16
 
18
- # deploy_test_on_all_hosts_as runs the command(s) return by '&block' on all hosts in self.hosts
17
+ def deploy_test_file_exists_on_hosts_as(userid, host_list, path, success_msg = nil)
18
+ deploy_test_on_hosts_as(userid, host_list, /^\s*success\s*$/, success_msg || "path #{path} should exist") do
19
+ "test -s #{path} && echo success || echo fail"
20
+ end
21
+ end
22
+
23
+ # deploy_test_on_hosts_as runs the command(s) return by '&block' on hosts in 'host_list'
19
24
  # as the specified user 'userid'.
20
25
  # For each host, an error is declared if EITHER STDOUT does not match 'match_expr_or_str'
21
26
  # OR if the command returns anything on STDERR.
22
27
  # 'match_expr_or_str' can be a Regexp or a string (which will be converted to a Regexp)
23
- def deploy_test_on_all_hosts_as(userid, match_expr_or_str, success_msg, &block)
28
+ def deploy_test_on_hosts_as userid, host_list, match_expr_or_str, success_msg, &block
24
29
  ret = true
25
- self.hosts.each do |host|
30
+ host_list = [host_list] if host_list.is_a? String
31
+ host_list.uniq.each do |host|
26
32
  ret &= deploy_test_in_ssh_session_as userid, host, match_expr_or_str, success_msg, &block
27
33
  end
28
34
  ret
29
35
  end
30
36
 
31
- # deploy_test_in_ssh_session host, match_exp_or_string, success_msg, &block runs the command
32
- # returned by 'block.call' on the specified host as user 'self.host_admin'.
33
- # declares an error if EITHER STDOUT does not match 'match' OR STDERR returns anything
34
- # 'match' can be a Regexp or a string (which will be converted to a Regexp)
35
- def deploy_test_in_ssh_session(host, match, success_msg, &block)
36
- deploy_test_in_ssh_session_as(self.host_admin, host, match, success_msg, &block)
37
- end
38
-
39
37
  # deploy_test_in_ssh_session_as runs the command(s) return by '&block' on the specified host
40
38
  # as user 'userid'
41
39
  # declares an error if EITHER STDOUT does not match 'match' OR STDERR returns anything
@@ -44,19 +42,19 @@ module TddDeploy
44
42
  match = Regexp.new(match) if match.is_a? String
45
43
  raise ArgumentError, 'match expression cannot be empty' if match =~ ''
46
44
 
47
- rsp, err_rsp, cmd = run_in_ssh_session_as(userid, host, &block)
45
+ rsp, err_rsp, cmd = run_in_ssh_session_on_host_as(userid, host, &block)
48
46
 
49
47
  result = err_rsp.nil?
50
48
 
51
49
  prefix = "user@host: #{userid}@#{host}: #{success_msg}"
52
50
 
53
- fail "#{prefix}: command generated error data:\n" +
51
+ fail host, "#{prefix}: command generated error data:\n" +
54
52
  " command: #{cmd}\n stdout: '#{rsp}'\n stderr: '#{err_rsp}'" if err_rsp
55
53
 
56
54
  if rsp.nil?
57
- fail "#{prefix}: stdout is empty for command '#{cmd}'"
55
+ fail host, "#{prefix}: stdout is empty for command '#{cmd}'"
58
56
  result &= false
59
- elsif !assert_match match, rsp, prefix
57
+ elsif !assert_match host, match, rsp, prefix
60
58
  result &= false
61
59
  end
62
60