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