tdd_deploy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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