tdd_deploy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Capfile +12 -0
- data/Gemfile +9 -0
- data/bin/tdd_deploy_context +132 -0
- data/bin/tdd_deploy_server.rb +6 -0
- data/config.ru +6 -0
- data/lib/tasks/tdd_deploy.rake +24 -0
- data/lib/tdd_deploy/assertions.rb +146 -0
- data/lib/tdd_deploy/base.rb +62 -0
- data/lib/tdd_deploy/deploy_test_methods.rb +65 -0
- data/lib/tdd_deploy/environ.rb +272 -0
- data/lib/tdd_deploy/host_tests/host_connection.rb +29 -0
- data/lib/tdd_deploy/host_tests/remote_ip_tables.rb +34 -0
- data/lib/tdd_deploy/host_tests/remote_monit.rb +17 -0
- data/lib/tdd_deploy/host_tests/remote_nginx.rb +17 -0
- data/lib/tdd_deploy/host_tests/remote_postfix.rb +23 -0
- data/lib/tdd_deploy/host_tests/remote_postgresql.rb +17 -0
- data/lib/tdd_deploy/railengine.rb +10 -0
- data/lib/tdd_deploy/run_methods.rb +126 -0
- data/lib/tdd_deploy/server.rb +58 -0
- data/lib/tdd_deploy/site_tests/site_layout.rb +46 -0
- data/lib/tdd_deploy/site_tests/site_user.rb +14 -0
- data/lib/tdd_deploy/version.rb +3 -0
- data/lib/tdd_deploy.rb +12 -0
- data/tests/test_assertions.rb +62 -0
- data/tests/test_base.rb +20 -0
- data/tests/test_colored_tests.rb +47 -0
- data/tests/test_environ.rb +153 -0
- data/tests/test_helpers.rb +10 -0
- data/tests/test_host_tests.rb +31 -0
- data/tests/test_remote_ip_tables.rb +27 -0
- data/tests/test_remote_monit.rb +27 -0
- data/tests/test_remote_nginx.rb +27 -0
- data/tests/test_remote_postfix.rb +27 -0
- data/tests/test_remote_postgresql.rb +27 -0
- data/tests/test_run_methods.rb +89 -0
- data/tests/test_server.rb +46 -0
- data/tests/test_site_layout.rb +34 -0
- data/tests/test_site_user.rb +21 -0
- data/tests/test_tdd_deploy.rb +17 -0
- data/tests/test_tdd_deploy_context.rb +35 -0
- data/tests/test_tdd_deploy_server.rb +59 -0
- data/tests/test_test_deploy_methods.rb +97 -0
- 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,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
|