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