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
data/Capfile
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
tdd_lib_path = File.expand_path('../../lib', __FILE__)
|
5
|
+
unless $:.include? tdd_lib_path
|
6
|
+
$:.unshift tdd_lib_path
|
7
|
+
end
|
8
|
+
require 'tdd_deploy/environ'
|
9
|
+
# require 'curses'
|
10
|
+
|
11
|
+
class TddDeployEnv
|
12
|
+
include TddDeploy::Environ
|
13
|
+
|
14
|
+
attr_accessor :modified
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
read_env || reset_env
|
18
|
+
end
|
19
|
+
|
20
|
+
def show_env
|
21
|
+
puts "============================================="
|
22
|
+
self.env_types.keys.each do |k|
|
23
|
+
v = self.send(k.to_sym)
|
24
|
+
printf "%-20s: %s\n", k, v
|
25
|
+
end
|
26
|
+
if (self.web_hosts.nil? && self.db_hosts.nil?) || self.web_hosts == self.db_hosts
|
27
|
+
printf "\n\nSpecial Key\n%-20s: %s\n", 'hosts', self.hosts, "set 'hosts' to set both web & db hosts"
|
28
|
+
end
|
29
|
+
puts "\nModified & Not Saved: #{self.modified.join(', ')}\n" if self.modified
|
30
|
+
self.flash
|
31
|
+
"(Env Key OR S[ave] to Save OR Q[uit] Quit + Save OR E[xit] to quit w/o Saving)\n? "
|
32
|
+
end
|
33
|
+
|
34
|
+
def modified?
|
35
|
+
self.modified
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_to_modified *args
|
39
|
+
@modified ||= []
|
40
|
+
@modified += args
|
41
|
+
end
|
42
|
+
|
43
|
+
def flash=(value)
|
44
|
+
@flash ||= ''
|
45
|
+
@flash += "#{value}\n"
|
46
|
+
end
|
47
|
+
|
48
|
+
def flash
|
49
|
+
puts "\n#{@flash}\n\n" if @flash
|
50
|
+
@flash = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def save
|
54
|
+
if self.modified?
|
55
|
+
self.flash = 'Updates Saved'
|
56
|
+
self.save_env
|
57
|
+
end
|
58
|
+
self.modified = false
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_cmd(cmd)
|
62
|
+
puts cmd
|
63
|
+
unless cmd =~ /^\s*(\w+)(\s+.*?)\s*$/i
|
64
|
+
self.flash = "unable to parse command: '#{cmd}'" unless cmd.strip.empty?
|
65
|
+
return
|
66
|
+
end
|
67
|
+
|
68
|
+
key_prefix = $1
|
69
|
+
param_value = $2.strip
|
70
|
+
|
71
|
+
key_regx = Regexp.new('^' + key_prefix, Regexp::IGNORECASE)
|
72
|
+
matching_keys = self.env_types.keys.select { |k| key_regx.match(k) }
|
73
|
+
|
74
|
+
if matching_keys.size > 1 && matching_keys.include?(key_prefix)
|
75
|
+
matching_keys = [key_prefix]
|
76
|
+
end
|
77
|
+
|
78
|
+
case matching_keys.size
|
79
|
+
when 0
|
80
|
+
if key_prefix == 'hosts'
|
81
|
+
self.hosts = param_value
|
82
|
+
self.flash = "Set hosts to '#{param_value}'"
|
83
|
+
self.add_to_modified('hosts', 'web_hosts', 'db_hosts')
|
84
|
+
else
|
85
|
+
self.flash = "No environment variable matches #{key_prefix}"
|
86
|
+
end
|
87
|
+
when 1
|
88
|
+
key = matching_keys.first
|
89
|
+
self.send "#{key}=".to_sym, param_value
|
90
|
+
self.add_to_modified(key)
|
91
|
+
self.flash = "#{key} updated"
|
92
|
+
else
|
93
|
+
self.flash = "ambiguous match: #{matching_keys.join(', ')}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
tdd_deploy_env = TddDeployEnv.new
|
99
|
+
|
100
|
+
begin
|
101
|
+
STDOUT.write tdd_deploy_env.show_env
|
102
|
+
STDOUT.flush
|
103
|
+
|
104
|
+
if STDIN.eof?
|
105
|
+
STDERR.write "Unexpected End of Input - aborting"
|
106
|
+
STDERR.write " - Discarded Unsaved Edits [#{tdd_deploy_env.modified.join(', ')}]" if tdd_deploy_env.modified?
|
107
|
+
STDERR.write "\n"
|
108
|
+
STDERR.flush
|
109
|
+
break
|
110
|
+
end
|
111
|
+
cmd = STDIN.readline.strip
|
112
|
+
puts cmd
|
113
|
+
|
114
|
+
if cmd =~ /^q(uit)?$/i
|
115
|
+
tdd_deploy_env.save
|
116
|
+
tdd_deploy_env.flash = 'Quit Entered'
|
117
|
+
break
|
118
|
+
elsif cmd =~ /^s(ave)?$/i
|
119
|
+
tdd_deploy_env.save
|
120
|
+
elsif cmd =~ /^e(xit)?$/i
|
121
|
+
tdd_deploy_env.flash = "Exit Entered"
|
122
|
+
tdd_deploy_env.flash = "Discarded Edits [#{tdd_deploy_env.modified.join(', ')}]" if tdd_deploy_env.modified?
|
123
|
+
break
|
124
|
+
else
|
125
|
+
tdd_deploy_env.parse_cmd(cmd)
|
126
|
+
end
|
127
|
+
# rescue Exception => e
|
128
|
+
# puts "Rescuing!!!!: #{e}"
|
129
|
+
# exit 1
|
130
|
+
end until STDIN.closed?
|
131
|
+
|
132
|
+
tdd_deploy_env.flash
|
data/config.ru
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
LIB_PATH = File.expand_path('../..', __FILE__)
|
3
|
+
LOCAL_TDD_DIR = File.join('lib', 'tdd_deploy')
|
4
|
+
|
5
|
+
namespace :tdd_deploy do
|
6
|
+
desc "uninstall removes gem supplied version of tests in lib/tdd_deploy/host_tests & site_tests. Doesn't touch lib/tdd_deploy/local_tests"
|
7
|
+
task :uninstall do
|
8
|
+
['host_tests', 'site_tests'].each do |target_dir|
|
9
|
+
target_path = File.join(LOCAL_TDD_DIR, target_dir)
|
10
|
+
FileUtils.rm_r target_path if File.exists? target_path
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "install tdd_deploy copies tests for hosts and sites from #{LIB_PATH} to lib/tdd_deploy/"
|
15
|
+
task :install do
|
16
|
+
LOCAL_TDD_DIR = File.join('lib', 'tdd_deploy')
|
17
|
+
[ 'lib', LOCAL_TDD_DIR, File.join(LOCAL_TDD_DIR, 'local_tests')].each do |path|
|
18
|
+
Dir.mkdir(path) unless File.exists? path
|
19
|
+
end
|
20
|
+
[ File.join(LIB_PATH, 'tdd_deploy', 'host_tests'), File.join(LIB_PATH, 'tdd_deploy', 'site_tests')].each do |src|
|
21
|
+
FileUtils.cp_r src, LOCAL_TDD_DIR
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module TddDeploy
|
2
|
+
module Assertions
|
3
|
+
GREEN = '#0c0'
|
4
|
+
RED = '#c00'
|
5
|
+
WRAP_ELT_TAG = 'p'
|
6
|
+
# TddDeploy re-implements popular assertions so that they can be used
|
7
|
+
# in multi-host testing.
|
8
|
+
#
|
9
|
+
# These assertions differ from usual TDD assertions in that they do not stop
|
10
|
+
# tests after failing. Rather they continue and accumulate failure counts and messages
|
11
|
+
# which can be displayed using *announce_test_results()*.
|
12
|
+
#
|
13
|
+
# all assertions return boolean *true* or *false*
|
14
|
+
|
15
|
+
# == Stats
|
16
|
+
#
|
17
|
+
# Stats is a class which acts as a singleton container for test statistics & messages
|
18
|
+
# test_count, messages, failiure count, and failure messages are all instance variables
|
19
|
+
# of Stats. This avoids nasty complications which can come us when using instance,
|
20
|
+
# class, and class-instance variables because the actual variables are defined when they
|
21
|
+
# are initialized - which can easily differ from where they are declared.
|
22
|
+
class Stats
|
23
|
+
class << self
|
24
|
+
attr_accessor :test_count, :failure_count, :failure_messages, :test_messages
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
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
|
+
# Assertions all return true or false. The last parameter is always the assertions
|
56
|
+
# message and is optional.
|
57
|
+
#
|
58
|
+
# assert(prediccate, msg) returns true if prediccate is true, else adds *msg*
|
59
|
+
# to failure messages and returns false
|
60
|
+
def assert predicate, msg
|
61
|
+
assert_primative predicate, msg
|
62
|
+
end
|
63
|
+
|
64
|
+
def assert_equal expect, value, msg
|
65
|
+
assert_primative expect == value, msg
|
66
|
+
end
|
67
|
+
|
68
|
+
def assert_match regx, value, msg
|
69
|
+
regx = Regexp.new(regx.to_s) unless regx.instance_of? Regexp
|
70
|
+
assert_primative regx.match(value), msg
|
71
|
+
end
|
72
|
+
|
73
|
+
def assert_nil value, msg
|
74
|
+
assert_primative value.nil?, msg
|
75
|
+
end
|
76
|
+
|
77
|
+
def assert_not_nil value, msg
|
78
|
+
assert_primative !value.nil?, msg
|
79
|
+
end
|
80
|
+
|
81
|
+
def assert_raises exception = Exception, msg, &block
|
82
|
+
begin
|
83
|
+
block.call
|
84
|
+
rescue exception => e
|
85
|
+
return true
|
86
|
+
end
|
87
|
+
assert_primative false, msg
|
88
|
+
end
|
89
|
+
|
90
|
+
def refute predicate, msg
|
91
|
+
assert_primative !predicate, msg
|
92
|
+
end
|
93
|
+
|
94
|
+
def refute_equal expect, value, msg
|
95
|
+
assert_primative expect != value, msg
|
96
|
+
end
|
97
|
+
|
98
|
+
def pass msg
|
99
|
+
assert_primative true, msg
|
100
|
+
end
|
101
|
+
|
102
|
+
def fail msg
|
103
|
+
assert_primative false, msg
|
104
|
+
end
|
105
|
+
|
106
|
+
# private methods
|
107
|
+
private
|
108
|
+
def assert_primative predicate, msg
|
109
|
+
predicate ? test_passed("Passed: #{msg}") : test_failed("Failed: #{msg}")
|
110
|
+
predicate
|
111
|
+
end
|
112
|
+
|
113
|
+
# 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)
|
118
|
+
end
|
119
|
+
|
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
|
130
|
+
end
|
131
|
+
|
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
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.included(mod)
|
140
|
+
Stats.test_count = 0
|
141
|
+
Stats.failure_count = 0
|
142
|
+
Stats.failure_messages = []
|
143
|
+
Stats.test_messages = []
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'tdd_deploy/assertions'
|
2
|
+
require 'tdd_deploy/environ'
|
3
|
+
require 'tdd_deploy/run_methods'
|
4
|
+
require 'tdd_deploy/deploy_test_methods'
|
5
|
+
# = TddDeploy
|
6
|
+
#
|
7
|
+
# TddDeploy provides methods for testing the provisioning of remote hosts
|
8
|
+
# and Rails instances running as virtual hosts
|
9
|
+
#
|
10
|
+
# Tests are simple to write.
|
11
|
+
#
|
12
|
+
# Step 1: require 'tdd_deploy' and then subclass TddDeploy::Base
|
13
|
+
# Step 2: write tests using the methods: *run_on_all_hosts* and *run_on_all_hosts_as*
|
14
|
+
# Step 3: run tests and fix installation until all tests pass
|
15
|
+
#
|
16
|
+
# These tests do not guarantee that anything will work. They only test to see if the files
|
17
|
+
# are installed and that communication works to all hosts the site runs on.
|
18
|
+
#
|
19
|
+
# == TddDeploy::Base
|
20
|
+
#
|
21
|
+
# TddDeploy::Base is a class which includes all the TddDeploy modules
|
22
|
+
# and initializes the environment.
|
23
|
+
#
|
24
|
+
# it is meant to be subclassed for individual host and site tests.
|
25
|
+
#
|
26
|
+
# class HostFacilityTest < TddDeploy::Base
|
27
|
+
# def test_for_file
|
28
|
+
# deploy_test_on_all_hosts_as user_id, match_string_or_regx, err_msg { command }
|
29
|
+
# end
|
30
|
+
# etc
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# NOTE: Derived classes which provide an **initialize** method should call super
|
34
|
+
# to ensure that the environment is set. See TddDeploy::Base to see what the
|
35
|
+
# parent initializer does.
|
36
|
+
|
37
|
+
module TddDeploy
|
38
|
+
class Base
|
39
|
+
include TddDeploy::Assertions
|
40
|
+
include TddDeploy::Environ
|
41
|
+
include TddDeploy::RunMethods
|
42
|
+
include TddDeploy::DeployTestMethods
|
43
|
+
|
44
|
+
# args are ignored, unless the args.last is a Hash. If it is passed to *set_env*.
|
45
|
+
# This allows derived classes to have their own arguments as well as allowing
|
46
|
+
# the environment values to modified in a uniform way.
|
47
|
+
def initialize *args
|
48
|
+
self.env_hash || read_env || reset_env
|
49
|
+
set_env(args.pop) if args.last.is_a? Hash
|
50
|
+
end
|
51
|
+
|
52
|
+
# gather up all descendents so we know what tests to run
|
53
|
+
class <<self
|
54
|
+
attr_accessor :children
|
55
|
+
|
56
|
+
def inherited(child)
|
57
|
+
self.children ||= []
|
58
|
+
self.children << child
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'tdd_deploy/assertions'
|
2
|
+
require 'tdd_deploy/run_methods'
|
3
|
+
|
4
|
+
module TddDeploy
|
5
|
+
module DeployTestMethods
|
6
|
+
include TddDeploy::Assertions
|
7
|
+
include TddDeploy::RunMethods
|
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, err_msg, &block)
|
15
|
+
deploy_test_on_all_hosts_as self.host_admin, match_expr_or_str, err_msg, &block
|
16
|
+
end
|
17
|
+
|
18
|
+
# deploy_test_on_all_hosts_as runs the command(s) return by '&block' on all hosts in self.hosts
|
19
|
+
# as the specified user 'userid'.
|
20
|
+
# For each host, an error is declared if EITHER STDOUT does not match 'match_expr_or_str'
|
21
|
+
# OR if the command returns anything on STDERR.
|
22
|
+
# '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, err_msg, &block)
|
24
|
+
ret = true
|
25
|
+
self.hosts.each do |host|
|
26
|
+
ret &= deploy_test_in_ssh_session_as userid, host, match_expr_or_str, err_msg, &block
|
27
|
+
end
|
28
|
+
ret
|
29
|
+
end
|
30
|
+
|
31
|
+
# deploy_test_in_ssh_session host, match_exp_or_string, err_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, err_msg, &block)
|
36
|
+
deploy_test_in_ssh_session_as(self.host_admin, host, match, err_msg, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
# deploy_test_in_ssh_session_as runs the command(s) return by '&block' on the specified host
|
40
|
+
# as user 'userid'
|
41
|
+
# declares an error if EITHER STDOUT does not match 'match' OR STDERR returns anything
|
42
|
+
# 'match' can be a Regexp or a string (which will be converted to a Regexp)
|
43
|
+
def deploy_test_in_ssh_session_as(userid, host, match, err_msg, &block)
|
44
|
+
match = Regexp.new(match) if match.is_a? String
|
45
|
+
raise ArgumentError, 'match expression cannot be empty' if match =~ ''
|
46
|
+
|
47
|
+
rsp, err_rsp, cmd = run_in_ssh_session_as(userid, host, &block)
|
48
|
+
|
49
|
+
result = err_rsp.nil?
|
50
|
+
|
51
|
+
prefix = "user@host: #{userid}@#{host}"
|
52
|
+
|
53
|
+
fail "<pre>\n#{prefix}: command generated error data:\n" +
|
54
|
+
" command: #{cmd}\n stdout: '#{rsp}'\n stderr: '#{err_rsp}'\n</pre>" if err_rsp
|
55
|
+
|
56
|
+
if !assert_not_nil rsp, "#{prefix}: stdout is empty for command '#{cmd}'"
|
57
|
+
result &= false
|
58
|
+
elsif !assert_match match, rsp, "#{prefix}: #{err_msg}\n rsp: #{rsp}"
|
59
|
+
result &= false
|
60
|
+
end
|
61
|
+
|
62
|
+
result
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|