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
data/Capfile ADDED
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+
3
+ set :user, "mike"
4
+
5
+ role :hosts, "arch"
6
+ #role :hosts, "ubuntu"
7
+ # role :hosts, 'li371-193.members.linode.com'
8
+
9
+ desc "Test to See if we have root access on hosts"
10
+ task :check_host_access do
11
+
12
+ end
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
2
+
3
+ gem 'ZenTest', "~> 4.5.0"
4
+ # gem 'redgreen'
5
+ gem 'autotest-growl'
6
+ gem 'autotest-fsevent'
7
+
8
+ gem 'capistrano'
9
+ gem 'net-ping'
@@ -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
@@ -0,0 +1,6 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # $:.unshift File.expand_path('../lib', __FILE__)
4
+ CONFIG_RU = File.expand_path('../../config.ru', __FILE__)
5
+
6
+ system "rackup --debug #{CONFIG_RU}"
data/config.ru ADDED
@@ -0,0 +1,6 @@
1
+ $:.unshift File.expand_path('../lib', __FILE__)
2
+
3
+ require 'rack'
4
+ require 'tdd_deploy/server'
5
+
6
+ Rack::Server.start :Port => 9292, :app => TddDeploy::Server.new
@@ -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