tdd_deploy 0.0.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 (43) hide show
  1. data/Capfile +12 -0
  2. data/Gemfile +9 -0
  3. data/bin/tdd_deploy_context +132 -0
  4. data/bin/tdd_deploy_server.rb +6 -0
  5. data/config.ru +6 -0
  6. data/lib/tasks/tdd_deploy.rake +24 -0
  7. data/lib/tdd_deploy/assertions.rb +146 -0
  8. data/lib/tdd_deploy/base.rb +62 -0
  9. data/lib/tdd_deploy/deploy_test_methods.rb +65 -0
  10. data/lib/tdd_deploy/environ.rb +272 -0
  11. data/lib/tdd_deploy/host_tests/host_connection.rb +29 -0
  12. data/lib/tdd_deploy/host_tests/remote_ip_tables.rb +34 -0
  13. data/lib/tdd_deploy/host_tests/remote_monit.rb +17 -0
  14. data/lib/tdd_deploy/host_tests/remote_nginx.rb +17 -0
  15. data/lib/tdd_deploy/host_tests/remote_postfix.rb +23 -0
  16. data/lib/tdd_deploy/host_tests/remote_postgresql.rb +17 -0
  17. data/lib/tdd_deploy/railengine.rb +10 -0
  18. data/lib/tdd_deploy/run_methods.rb +126 -0
  19. data/lib/tdd_deploy/server.rb +58 -0
  20. data/lib/tdd_deploy/site_tests/site_layout.rb +46 -0
  21. data/lib/tdd_deploy/site_tests/site_user.rb +14 -0
  22. data/lib/tdd_deploy/version.rb +3 -0
  23. data/lib/tdd_deploy.rb +12 -0
  24. data/tests/test_assertions.rb +62 -0
  25. data/tests/test_base.rb +20 -0
  26. data/tests/test_colored_tests.rb +47 -0
  27. data/tests/test_environ.rb +153 -0
  28. data/tests/test_helpers.rb +10 -0
  29. data/tests/test_host_tests.rb +31 -0
  30. data/tests/test_remote_ip_tables.rb +27 -0
  31. data/tests/test_remote_monit.rb +27 -0
  32. data/tests/test_remote_nginx.rb +27 -0
  33. data/tests/test_remote_postfix.rb +27 -0
  34. data/tests/test_remote_postgresql.rb +27 -0
  35. data/tests/test_run_methods.rb +89 -0
  36. data/tests/test_server.rb +46 -0
  37. data/tests/test_site_layout.rb +34 -0
  38. data/tests/test_site_user.rb +21 -0
  39. data/tests/test_tdd_deploy.rb +17 -0
  40. data/tests/test_tdd_deploy_context.rb +35 -0
  41. data/tests/test_tdd_deploy_server.rb +59 -0
  42. data/tests/test_test_deploy_methods.rb +97 -0
  43. metadata +146 -0
@@ -0,0 +1,272 @@
1
+ module TddDeploy
2
+
3
+ # == module TddDeploy::Environ
4
+ #
5
+ # provides management for host provisioning and deployment variables - aka 'the environment'.
6
+ # The 'enviornment' - as used here - is not the Ruby ENV hash
7
+ #
8
+ # three types of variables are supported
9
+ # :int - which are integers
10
+ # :string - which are strings
11
+ # :list - which are input as comma separated strings [the actual separator is /[\s,]+/ ]
12
+ # which are used internally as arrays of strings. So, to set a :list variable you
13
+ # write 'foo = bar,baz,boop', and when you use it you get back ['bar', 'baz', 'boop']
14
+ # (this inanity was done so we could save the environment as a simple ascii file)
15
+ #
16
+ # Environment variables:
17
+ #
18
+ # === Integer variables
19
+ # * 'ssh_timeout' - timeout in seconds used to terminate remote commands fired off by ssh
20
+ # * 'site_base_port' - Base port for site servers. For example, if a pack of mongrels or thin
21
+ # servers provide the rails end of the web site, they listen on 'site_base_port', +1, etd
22
+ # * 'site_num_servers' - number of mongrels or thins to spin up
23
+ #
24
+ # === String variables
25
+ # * 'host_admin' - user name used on remote hosts. Should not be root, but should be in /etc/sudoers
26
+ # * 'local_admin' - user name of on local hosts which can ssh into remote hosts via public key authentication
27
+ # * 'local_admin_email' - email of local admin who should receive monitoring emails
28
+ # * 'site' - name of site This should satisfy /[a-z][a-z0-9_]*.
29
+ # * 'site_user' - name of site user. TddDeploy assumes that each site will have a unique user on the remote host.
30
+ # this makes it easy to tell nginx and monit where to find configuration files for each site [both
31
+ # know how to included globbed paths]
32
+ #
33
+ # === List Variables
34
+ # * 'hosts' - list of all hosts - defaults to balance_hosts + db_hosts + web_hosts
35
+ # * 'balance_hosts' - load balancing servers [may be empty, in which case 'hosts' is used]
36
+ # * 'db_hosts' - hosts which run the database server [may be empty, in which case 'hosts' is used]
37
+ # * 'web_hosts' - hosts which run the web server [may be empty, in which case 'hosts' is used]
38
+
39
+ module Environ
40
+
41
+ # == DataCache
42
+ #
43
+ # DataCache is a hack to provide a single shared data store for all classes which
44
+ # include TddDeploy::Environ
45
+ class DataCache
46
+ class << self
47
+ attr_accessor :env_hash, :env_types, :env_defaults
48
+ end
49
+ end
50
+
51
+ ENV_FNAME = 'site_host_setup.env'
52
+
53
+ # set up all the standard accessors
54
+ # lazy initialize DataCache.env_hash
55
+ def env_hash
56
+ read_env || reset_env unless defined?(DataCache.env_hash)
57
+ DataCache.env_hash
58
+ end
59
+
60
+ def env_hash=(hash)
61
+ raise ArgumentError.new("env_hash=(): arg must be a hash") unless hash.is_a? Hash
62
+ if !(tmp = hash.keys - DataCache.env_types.keys).empty?
63
+ raise ArgumentError.new("env_hash=(): Illegal Keys in value: #{tmp.join(',')}")
64
+ elsif !(tmp = DataCache.env_types.keys - hash.keys).empty?
65
+ raise ArgumentError.new("env_hash=(): Missing Keys in value: #{tmp.join(',')}")
66
+ else
67
+ DataCache.env_hash = hash
68
+ end
69
+ end
70
+
71
+ DataCache.env_types = {
72
+ 'ssh_timeout' => :int,
73
+ 'site_base_port' => :int,
74
+ 'site_num_servers' => :int,
75
+
76
+ 'host_admin' => :string,
77
+ 'local_admin' => :string,
78
+ 'local_admin_email' => :string,
79
+
80
+ 'site' => :string,
81
+ 'site_user' => :string,
82
+
83
+ # 'hosts' => :list,
84
+ 'balance_hosts' => :list,
85
+ 'db_hosts' => :list,
86
+ 'web_hosts' => :list,
87
+ }
88
+
89
+ DataCache.env_defaults ||= {
90
+ 'ssh_timeout' => 5,
91
+ 'site_base_port' => 8000,
92
+ 'site_num_servers' => 3,
93
+
94
+ 'host_admin' => "host_admin",
95
+ 'local_admin' => "local_admin",
96
+ 'local_admin_email' => "local_admin@bogus.tld",
97
+
98
+ 'site' => "site",
99
+ 'site_user' => "site_user",
100
+
101
+ # 'hosts' => "bar,foo",
102
+ 'balance_hosts' => '',
103
+ 'db_hosts' => 'bar,foo',
104
+ 'web_hosts' => 'bar,foo',
105
+ }
106
+
107
+ def env_types
108
+ DataCache.env_types
109
+ end
110
+
111
+ def env_defaults
112
+ DataCache.env_defaults
113
+ end
114
+
115
+ # set_env(value_hash {}) - convenience method which sets values of the environment
116
+ # hash using a hash rather than one-at-a-time
117
+ def set_env(value_hash = {})
118
+ DataCache.env_hash ||= {}
119
+ value_hash.each do |k, v|
120
+ k = k.to_s
121
+ case self.env_types[k]
122
+ when :int then DataCache.env_hash[k] = v.to_i
123
+ when :string then DataCache.env_hash[k] = v.to_s
124
+ when :list then DataCache.env_hash[k] = self.str_to_list(v)
125
+ else
126
+ if k == 'hosts'
127
+ if DataCache.env_hash['web_hosts'] == DataCache.env_hash['db_hosts']
128
+ DataCache.env_hash['web_hosts'] =
129
+ DataCache.env_hash['db_hosts'] = self.str_to_list(v)
130
+ else
131
+ raise RuntimeError.new("#{self}#reset_env(): Cannot assign value to 'hosts' if web_hosts &/or db_hosts already set.\n web_hosts: #{DataCache.env_hash['web_hosts']}\n db_hosts: #{DataCache.env_hash['db_hosts']}")
132
+ # raise RuntimeError.new("Cannot change hosts key if web_hosts != db_hosts")
133
+ end
134
+ else
135
+ raise ArgumentError.new("#{self}#reset_env(): Illegal environment key: #{k}")
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ def clear_env
142
+ DataCache.env_hash = {}
143
+ end
144
+
145
+ # reset_env resets env_hash to env_defaults
146
+ def reset_env
147
+ clear_env
148
+ set_env self.env_defaults
149
+ end
150
+
151
+ # reads the environment from TddDeploy::Environ::ENV_FNAME (site_host_setup.env) if the file exists
152
+ # someplace between the current directory and the root of the filesystem
153
+ def read_env
154
+ dir_path = Dir.pwd
155
+ DataCache.env_hash ||= {}
156
+ loop do
157
+ path = File.join dir_path, TddDeploy::Environ::ENV_FNAME
158
+ if File.exists? TddDeploy::Environ::ENV_FNAME
159
+ line_no = 0
160
+ if f = File.new(path, 'r')
161
+ begin
162
+ f.each do |line|
163
+ line_no += 1
164
+ if line =~ /^\s*(\w+)\s*=\s*(.*?)\s*$/
165
+ key = $1.downcase
166
+ if self.env_types.keys.include? key
167
+ self.send "#{key}=".to_sym, $2
168
+ # self.env_hash[key] = self.env_types[key] == :list ? self.str_to_list($2) : $2.to_s
169
+ else
170
+ raise ArugmentError.new("TddDeploy::Environ#read_env: Error in #{TddDeploy::Error::ENV_FNAME}: #{line_no}: Illegal Key: #{key}")
171
+ end
172
+ else
173
+ raise ArugmentError.new("TddDeploy::Environ#read_env: Error in #{TddDeploy::Error::ENV_FNAME}: #{line_no}: Unmatched Line: #{line}}")
174
+ end
175
+ end
176
+ ensure
177
+ f.close
178
+ end
179
+ return self.env_hash
180
+ else
181
+ raise RuntimeError.new("Unable to open #{path} for reading")
182
+ end
183
+ elsif dir_path.length <= 1
184
+ # reached root level, so initialize to defaults and exit
185
+ return nil
186
+ else
187
+ # move to parent directory
188
+ dir_path = File.expand_path('..', dir_path)
189
+ end
190
+ end
191
+ nil
192
+ end
193
+
194
+ def str_to_list str
195
+ case
196
+ when str.is_a?(String) then str.split(/[\s,]+/).uniq.sort
197
+ when str.is_a?(Array) then str.uniq.sort
198
+ else
199
+ raise ArgumentError.new("str_to_list: #{str}")
200
+ end
201
+ end
202
+
203
+ def list_to_str key
204
+ tmp = self.env_hash[key]
205
+ tmp.is_a?(Array) ? tmp.join(',') : tmp.to_s
206
+ end
207
+
208
+ # saves the current environment in the current working directory in the file
209
+ # 'site_host_setup.env' [aka TddDeploy::Environ::ENV_FNAME]
210
+ def save_env
211
+ f = File.new(TddDeploy::Environ::ENV_FNAME, "w")
212
+ self.env_types.keys.each do |k|
213
+ v = self.env_hash[k] || ''
214
+ case self.env_types[k]
215
+ when :int then f.write "#{k}=#{v}\n"
216
+ when :string then f.write "#{k}=#{v}\n"
217
+ when :list then
218
+ f.write "#{k}=#{self.list_to_str(k)}\n" unless k == 'hosts'
219
+ else
220
+ raise RuntimeError("unknown key: #{k}")
221
+ end
222
+ end
223
+ f.close
224
+ end
225
+
226
+ # accessors for all defined env variables
227
+ def hosts
228
+ (self.web_hosts.to_a + self.db_hosts.to_a + self.balance_hosts.to_a).uniq.sort
229
+ end
230
+
231
+ def hosts=(list)
232
+ if (self.web_hosts.nil? && self.db_hosts.nil?) || self.web_hosts == self.db_hosts
233
+ self.web_hosts =
234
+ self.db_hosts = self.str_to_list(list)
235
+ else
236
+ raise RuntimeError.new("Cannot assign value to 'hosts' if web_hosts &/or db_hosts already set.\n web_hosts: #{self.web_hosts}\n db_hosts: #{self.db_hosts}")
237
+ end
238
+ end
239
+
240
+ # create accessors for all keys in env_types
241
+ tmp = ''
242
+ DataCache.env_types.each do |k, t|
243
+ tmp +=<<-EOF
244
+ def #{k}
245
+ self.env_hash['#{k}']
246
+ end
247
+ EOF
248
+ case DataCache.env_types[k]
249
+ when :int
250
+ tmp +=<<-EOF
251
+ def #{k}=(v)
252
+ self.env_hash['#{k}'] = v.to_i
253
+ end
254
+ EOF
255
+ when :string
256
+ tmp +=<<-EOF
257
+ def #{k}=(v)
258
+ self.env_hash['#{k}'] = v.to_s
259
+ end
260
+ EOF
261
+ when :list
262
+ tmp +=<<-EOF
263
+ def #{k}=(v)
264
+ self.env_hash['#{k}'] = self.str_to_list(v)
265
+ end
266
+ EOF
267
+ end
268
+ end
269
+
270
+ class_eval tmp
271
+ end
272
+ end
@@ -0,0 +1,29 @@
1
+ require 'tdd_deploy/base'
2
+
3
+ module TddDeploy
4
+ class HostConnection < TddDeploy::Base
5
+ # ping - pings all hosts
6
+ def ping
7
+ require 'net/ping'
8
+ result = true
9
+ self.hosts.each do |host|
10
+ result &= assert Net::Ping::External.new(host).ping?, "Host #{host} should respond to ping"
11
+ end
12
+ result
13
+ end
14
+
15
+ # ssh_login - attempts to log in as *host_admin* on all hosts from current user
16
+ def ssh_login
17
+ deploy_test_on_all_hosts "/home/#{self.host_admin}\n", "unable to connect via ssh" do
18
+ 'pwd'
19
+ end
20
+ end
21
+
22
+ # ssh_login_as_root - attempts to log in as *root* on all hosts from current user
23
+ def ssh_login_as_root
24
+ deploy_test_on_all_hosts_as 'root', '/root', "unable to connect as root via ssh" do
25
+ 'pwd'
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ require 'tdd_deploy/base'
2
+
3
+ module TddDeploy
4
+ # = TddDeploy::RemoteIpTables
5
+ #
6
+ # checks to see if iptables is working by attempting to connect to each host on a collection
7
+ # of 'interesting' ports. the ports probed are: 20, 23, 25, 53, 5432, 2812
8
+ #
9
+ class RemoteIpTables < TddDeploy::Base
10
+ # tcp_some_blocked_ports - checks TCP ports
11
+ def tcp_some_blocked_ports
12
+ self.hosts.each do |host|
13
+ # Linode seems to refuse to block 21 - FTP control
14
+ # [20, 21, 23, 25, 53, 5432, 2812].each do |port|
15
+ [20, 23, 25, 53, 5432, 2812].each do |port|
16
+ tcp_socket = TCPSocket.new(host, port) rescue 'failed'
17
+ assert_equal 'failed', tcp_socket, "Host: #{host}: Should not be able to connect via tcp to port #{port}"
18
+ end
19
+ end
20
+ end
21
+
22
+ # udp_some_blocked_ports - checks UDP ports
23
+ def udp_some_blocked_ports
24
+ self.hosts.each do |host|
25
+ # Linode seems to refuse to block 21 - FTP control
26
+ # [20, 21, 23, 25, 53, 5432, 2812].each do |port|
27
+ [20, 23, 25, 53, 5432, 2812].each do |port|
28
+ udp_socket = UDPSocket.new(host, port) rescue 'failed'
29
+ assert_equal 'failed', udp_socket, "Host: #{host}: Should not be able to connect via udp to port #{port}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ require 'tdd_deploy/base'
2
+
3
+ module TddDeploy
4
+ class RemoteMonit < TddDeploy::Base
5
+ def test_monit_installed
6
+ deploy_test_on_all_hosts '/usr/bin/monit', "monit is not installed" do
7
+ 'ls /usr/bin/monit'
8
+ end
9
+ end
10
+
11
+ def test_monit_running
12
+ deploy_test_on_all_hosts /\smonit\s/, "monit is not running" do
13
+ 'ps -p `cat /var/run/monit.pid`'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'tdd_deploy/base'
2
+
3
+ module TddDeploy
4
+ class RemoteNginx < TddDeploy::Base
5
+ def test_nginx_installed
6
+ deploy_test_on_all_hosts '/usr/sbin/nginx', "nginx is not installed" do
7
+ 'ls /usr/sbin/nginx'
8
+ end
9
+ end
10
+
11
+ def test_nginx_running
12
+ deploy_test_on_all_hosts /\snginx\s/, "nginx is not running" do
13
+ 'ps -p `cat /var/run/nginx.pid`'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ require 'tdd_deploy/base'
2
+
3
+ module TddDeploy
4
+ class RemotePostfix < TddDeploy::Base
5
+ def test_postfix_installed
6
+ deploy_test_on_all_hosts '/usr/sbin/postfix', "postfix is not installed" do
7
+ 'ls /usr/sbin/postfix'
8
+ end
9
+ end
10
+
11
+ def test_postfix_running
12
+ deploy_test_on_all_hosts_as 'root', /\smaster\s/, "postfix is not running" do
13
+ 'ps -p `cat /var/spool/postfix/pid/master.pid`'
14
+ end
15
+ end
16
+
17
+ # def test_postfix_accepts_mail
18
+ # deploy_test_on_all_hosts "mail works\n", 'postfix accepts mail' do |host, login, userid|
19
+ # "echo \"test mail\" | mail -s 'test mail' -r #{userid}@#{host} #{self.local_admin_email} && echo 'mail works'"
20
+ # end
21
+ # end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ require 'tdd_deploy/base'
2
+
3
+ module TddDeploy
4
+ class RemotePostgresql < TddDeploy::Base
5
+ def test_postgresql_installed
6
+ deploy_test_on_all_hosts "/usr/bin/postgres\n", 'postgres not installed' do |host, login, admin|
7
+ "ls /usr/bin/postgres"
8
+ end
9
+ end
10
+
11
+ def test_postgresql_running
12
+ deploy_test_on_all_hosts /postgres\s*\|\s*postgres/, "postgresql server not running" do
13
+ "psql --command='\\l' postgres postgres"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ module TddDeploy
2
+ class Engine < Rails::Engine
3
+ # engine_name "tdd_deploy"
4
+ rake_tasks do
5
+ load "tdd_deploy/../tasks/tdd_deploy.rake"
6
+ end
7
+ initializer 'active_support.add_tdd_deploy' do
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,126 @@
1
+ module TddDeploy
2
+ module RunMethods
3
+ require 'net/ssh'
4
+
5
+ # runs the output of the block on all hosts defined in self.hosts as user self.host_admin.
6
+ # Returns a hash of two element arrays containing output [stdout, stderr] returned from the command.
7
+ # Hash keys are host names as strings.
8
+ def run_on_all_hosts(&block)
9
+ run_on_all_hosts_as self.host_admin, &block
10
+ end
11
+
12
+ # Runs the output of the block on all hosts defined in self.hosts as user 'userid'.
13
+ # Returns a hash of two element arrays containing output [stdout, stderr] returned from the command.
14
+ # Hash keys are host names as strings.
15
+ def run_on_all_hosts_as(userid, &block)
16
+ results = {}
17
+ self.hosts.each do |host|
18
+ results[host] = run_in_ssh_session_as(userid, host, &block)
19
+ end
20
+ results
21
+ end
22
+
23
+ # Runs the command secified in &block on 'host' as user 'self.host_admin'.
24
+ # Returns an array [stdout, stderr] returned from the command.
25
+ def run_in_ssh_session(host, &block)
26
+ run_in_ssh_session_as(self.host_admin, host, &block)
27
+ end
28
+
29
+ # Runs the command secified in &block on 'host' as user 'userid'.
30
+ # Returns an array [stdout, stderr] returned from the command.
31
+ def run_in_ssh_session_as(userid, host, &block)
32
+ login = "#{userid}@#{host}"
33
+ match = Regexp.new(match) if match.is_a? String
34
+ raise ArgumentError, 'match expression cannot be empty' if match =~ ''
35
+
36
+ rsp = nil
37
+ err_rsp = nil
38
+ cmd = block.call(host, login, userid)
39
+
40
+ begin
41
+ ssh_session = Net::SSH.start(host, userid, :timeout => self.ssh_timeout)
42
+ raise "Unable to establish connecton to #{host} as #{userid}" if ssh_session.nil?
43
+
44
+ ssh_session.open_channel do |channel|
45
+ channel.exec(cmd) do |ch, success|
46
+ ch.on_data do |chn, data|
47
+ rsp ||= ''
48
+ rsp += data.to_s
49
+ end
50
+
51
+ ch.on_extended_data do |chn, data|
52
+ err_rsp ||= ''
53
+ err_rsp += data.to_s
54
+ end
55
+
56
+ end
57
+ end
58
+
59
+ # must do this or the channel only runs once
60
+ ssh_session.loop(5)
61
+
62
+ ssh_session.close
63
+ rescue Exception => e
64
+ err_rsp = "error talking to #{host} as #{userid}: #{e.message}"
65
+ ssh_session.shutdown! unless ssh_session.nil?
66
+ end
67
+ [rsp, err_rsp, cmd]
68
+ end
69
+
70
+ # run locally runs a comman locally and returns the output of stdout, stderr, and the command
71
+ # run in a 3 element array
72
+ #
73
+ # to send input to the subprocess, include the optional 'stdin_text' parameter. Don't
74
+ # forget to add newlines, if you need them.
75
+ def run_locally(stdin_text = nil, &block)
76
+ raise ArgumentError.new('block required') unless block_given?
77
+
78
+ cmd = block.call
79
+ # cmd = "echo '#{stdin_text}' | #{cmd}" if stdin_text
80
+
81
+ raise "Unable to run_locally - fork method not useable" unless Process.respond_to? :fork
82
+
83
+ # preload stdin if there is input to avoid a race condition
84
+ if stdin_text
85
+ stdin_pipe, child_stdin = IO.pipe if stdin_text
86
+ count = child_stdin.write(stdin_text.to_s)
87
+ child_stdin.close
88
+ end
89
+
90
+ child_stdout, stdout_pipe = IO.pipe
91
+ child_stderr, stderr_pipe = IO.pipe
92
+ unless (pid = Process.fork)
93
+ if stdin_text
94
+ STDIN.reopen(stdin_pipe)
95
+ else
96
+ STDIN.close
97
+ end
98
+ STDOUT.reopen(stdout_pipe)
99
+ STDERR.reopen(stderr_pipe)
100
+ begin
101
+ Process.exec ENV, cmd
102
+ rescue SystemCallError => e
103
+ STDERR.write("Unable to execute command '#{cmd}'\n #{e}")
104
+ ensure
105
+ exit
106
+ end
107
+ end
108
+
109
+ Process.wait
110
+
111
+ stdout = ''
112
+ stderr = ''
113
+ loop do
114
+ reads, writes, obands = IO.select([child_stdout, child_stderr], [], [], 1)
115
+ break if reads.nil?
116
+ stdout += child_stdout.read_nonblock(1024) if reads.include? child_stdout
117
+ stderr += child_stderr.read_nonblock(1024) if reads.include? child_stderr
118
+ end
119
+ stdout = nil if stdout.empty?
120
+ stderr = nil if stderr.empty?
121
+
122
+ [stdout, stderr, cmd]
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path('../lib', __FILE__)
3
+
4
+ require 'tdd_deploy'
5
+
6
+ module TddDeploy
7
+ class Server < TddDeploy::Base
8
+ LIB_DIR = File.expand_path('../..', __FILE__)
9
+ HOST_TESTS_DIR = File.join(Dir.pwd, 'lib', 'tdd_deploy', 'host_tests')
10
+ SITE_TESTS_DIR = File.join(Dir.pwd, 'lib', 'tdd_deploy', 'site_tests')
11
+ LOCAL_TESTS_DIR = File.join(Dir.pwd, 'lib', 'tdd_deploy', 'local_tests')
12
+
13
+ attr_accessor :test_classes
14
+
15
+ def initialize *args
16
+ @already_defined = TddDeploy.constants
17
+ super
18
+ end
19
+
20
+ def load_all_tests
21
+ if TddDeploy::Base.children == [self.class]
22
+ [TddDeploy::Server::HOST_TESTS_DIR, TddDeploy::Server::SITE_TESTS_DIR,
23
+ TddDeploy::Server::LOCAL_TESTS_DIR].each do |dir|
24
+ if File.exists?(dir)
25
+ puts "gathering tests from #{dir}"
26
+ Dir.new(dir).each do |fname|
27
+ require File.join(dir, fname) unless fname[0] == '.'
28
+ end
29
+ else
30
+ puts "skipping #{dir} - no such directory"
31
+ end
32
+ end
33
+ end
34
+ self.test_classes = TddDeploy::Base.children - [self.class]
35
+ end
36
+
37
+ def run_all_tests
38
+ load_all_tests
39
+
40
+ ret = true
41
+ self.test_classes.each do |klass|
42
+ obj = klass.new
43
+ # puts "#{klass}.instance_methods: #{klass.instance_methods(false)}"
44
+ klass.instance_methods(false).each do |func|
45
+ ret &= obj.send func.to_sym
46
+ end
47
+ end
48
+ ret
49
+ end
50
+
51
+ def call(env)
52
+ run_all_tests
53
+ body = ["<h1>TDD Test Results:</h1>", self.test_results]
54
+ return [200, {'Content-Length' => body.join('').length.to_s, 'Content-Type' => 'text/html'}, body]
55
+ end
56
+ end
57
+ end
58
+
@@ -0,0 +1,46 @@
1
+ require 'tdd_deploy/base'
2
+
3
+ module TddDeploy
4
+ # == TddDeploy::SiteLayout
5
+ #
6
+ # tests for the existence of several directories on all hosts as *site_user* in
7
+ # the *site_user* home directory.
8
+ #
9
+ # The sub directories tested for are:
10
+ #
11
+ # *site* - a directory named for the name of the site.
12
+ # *site*/releases - a standard directory used by Capistrano
13
+ # *site*/nginx.conf - an nginx configuratino fragment which tells nginx to
14
+ # proxy the site's *thin* servers
15
+ # *site*/monitrc - a monit configuration fragment which tells monit how to monitor
16
+ # the site's *thin* servers.
17
+ class SiteLayout < TddDeploy::Base
18
+ def test_site_subdir
19
+ deploy_test_on_all_hosts_as self.site_user, "#{self.site}/", \
20
+ "directory /home/#{self.site_user}/#{self.site} should exist" do
21
+ 'ls -F'
22
+ end
23
+ end
24
+
25
+ def test_releases_subdir
26
+ deploy_test_on_all_hosts_as self.site_user, "releases", \
27
+ "directory /home/#{self.site_user}/#{self.site}/releases should exist" do
28
+ "ls -F #{self.site}"
29
+ end
30
+ end
31
+
32
+ def test_monitrc
33
+ deploy_test_on_all_hosts_as self.site_user, 'monitrc', \
34
+ "file /home/#{self.site_user}/monitrc should exist" do
35
+ 'ls'
36
+ end
37
+ end
38
+
39
+ def test_nginx_conf
40
+ deploy_test_on_all_hosts_as self.site_user, 'nginx.conf', \
41
+ "file /home/#{self.site_user}/nginx.conf should exist" do
42
+ 'ls'
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,14 @@
1
+ require 'tdd_deploy/base'
2
+
3
+ module TddDeploy
4
+ # = TddDeploy::SiteUser
5
+ #
6
+ # tests all hosts to make sure that the local user can log on as *site_user*
7
+ class SiteUser < TddDeploy::Base
8
+ def test_login_as_site_user
9
+ deploy_test_on_all_hosts_as self.site_user, "/home/#{self.site_user}", "unable to log into host as #{self.site_user}" do
10
+ 'pwd'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module TddDeploy
2
+ VERSION = '0.0.1'
3
+ end