tdd_deploy 0.0.3 → 0.1.1

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.
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