beaker 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.simplecov +14 -0
  5. data/DOCUMENTING.md +167 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +17 -0
  8. data/README.md +332 -0
  9. data/Rakefile +121 -0
  10. data/beaker.gemspec +42 -0
  11. data/beaker.rb +10 -0
  12. data/bin/beaker +9 -0
  13. data/lib/beaker.rb +36 -0
  14. data/lib/beaker/answers.rb +29 -0
  15. data/lib/beaker/answers/version28.rb +104 -0
  16. data/lib/beaker/answers/version30.rb +194 -0
  17. data/lib/beaker/cli.rb +113 -0
  18. data/lib/beaker/command.rb +241 -0
  19. data/lib/beaker/command_factory.rb +21 -0
  20. data/lib/beaker/dsl.rb +85 -0
  21. data/lib/beaker/dsl/assertions.rb +87 -0
  22. data/lib/beaker/dsl/helpers.rb +625 -0
  23. data/lib/beaker/dsl/install_utils.rb +299 -0
  24. data/lib/beaker/dsl/outcomes.rb +99 -0
  25. data/lib/beaker/dsl/roles.rb +97 -0
  26. data/lib/beaker/dsl/structure.rb +63 -0
  27. data/lib/beaker/dsl/wrappers.rb +100 -0
  28. data/lib/beaker/host.rb +193 -0
  29. data/lib/beaker/host/aix.rb +15 -0
  30. data/lib/beaker/host/aix/file.rb +16 -0
  31. data/lib/beaker/host/aix/group.rb +35 -0
  32. data/lib/beaker/host/aix/user.rb +32 -0
  33. data/lib/beaker/host/unix.rb +54 -0
  34. data/lib/beaker/host/unix/exec.rb +15 -0
  35. data/lib/beaker/host/unix/file.rb +16 -0
  36. data/lib/beaker/host/unix/group.rb +40 -0
  37. data/lib/beaker/host/unix/pkg.rb +22 -0
  38. data/lib/beaker/host/unix/user.rb +32 -0
  39. data/lib/beaker/host/windows.rb +44 -0
  40. data/lib/beaker/host/windows/exec.rb +18 -0
  41. data/lib/beaker/host/windows/file.rb +15 -0
  42. data/lib/beaker/host/windows/group.rb +36 -0
  43. data/lib/beaker/host/windows/pkg.rb +26 -0
  44. data/lib/beaker/host/windows/user.rb +32 -0
  45. data/lib/beaker/hypervisor.rb +37 -0
  46. data/lib/beaker/hypervisor/aixer.rb +52 -0
  47. data/lib/beaker/hypervisor/blimper.rb +123 -0
  48. data/lib/beaker/hypervisor/fusion.rb +56 -0
  49. data/lib/beaker/hypervisor/solaris.rb +65 -0
  50. data/lib/beaker/hypervisor/vagrant.rb +118 -0
  51. data/lib/beaker/hypervisor/vcloud.rb +175 -0
  52. data/lib/beaker/hypervisor/vsphere.rb +80 -0
  53. data/lib/beaker/hypervisor/vsphere_helper.rb +200 -0
  54. data/lib/beaker/logger.rb +167 -0
  55. data/lib/beaker/network_manager.rb +73 -0
  56. data/lib/beaker/options_parsing.rb +323 -0
  57. data/lib/beaker/result.rb +55 -0
  58. data/lib/beaker/shared.rb +15 -0
  59. data/lib/beaker/shared/error_handler.rb +17 -0
  60. data/lib/beaker/shared/host_handler.rb +46 -0
  61. data/lib/beaker/shared/repetition.rb +28 -0
  62. data/lib/beaker/ssh_connection.rb +198 -0
  63. data/lib/beaker/test_case.rb +225 -0
  64. data/lib/beaker/test_config.rb +148 -0
  65. data/lib/beaker/test_suite.rb +288 -0
  66. data/lib/beaker/utils.rb +7 -0
  67. data/lib/beaker/utils/ntp_control.rb +42 -0
  68. data/lib/beaker/utils/repo_control.rb +92 -0
  69. data/lib/beaker/utils/setup_helper.rb +77 -0
  70. data/lib/beaker/utils/validator.rb +27 -0
  71. data/spec/beaker/command_spec.rb +94 -0
  72. data/spec/beaker/dsl/assertions_spec.rb +104 -0
  73. data/spec/beaker/dsl/helpers_spec.rb +230 -0
  74. data/spec/beaker/dsl/install_utils_spec.rb +70 -0
  75. data/spec/beaker/dsl/outcomes_spec.rb +43 -0
  76. data/spec/beaker/dsl/roles_spec.rb +86 -0
  77. data/spec/beaker/dsl/structure_spec.rb +60 -0
  78. data/spec/beaker/dsl/wrappers_spec.rb +52 -0
  79. data/spec/beaker/host_spec.rb +95 -0
  80. data/spec/beaker/logger_spec.rb +117 -0
  81. data/spec/beaker/options_parsing_spec.rb +37 -0
  82. data/spec/beaker/puppet_command_spec.rb +128 -0
  83. data/spec/beaker/ssh_connection_spec.rb +39 -0
  84. data/spec/beaker/test_case_spec.rb +6 -0
  85. data/spec/beaker/test_suite_spec.rb +44 -0
  86. data/spec/mocks_and_helpers.rb +34 -0
  87. data/spec/spec_helper.rb +15 -0
  88. metadata +359 -0
data/lib/beaker/cli.rb ADDED
@@ -0,0 +1,113 @@
1
+ module Beaker
2
+ class CLI
3
+ def initialize
4
+ @options = Beaker::Options.parse_args
5
+ @logger = Beaker::Logger.new(@options)
6
+ @options[:logger] = @logger
7
+
8
+ if not @options[:config]
9
+ report_and_raise(@logger, RuntimeError.new("Argh! There is no default for Config, specify one (-c or --config)!"), "CLI: initialize")
10
+ end
11
+
12
+ @logger.debug("Options")
13
+ @options.each do |opt, val|
14
+ if val and val != []
15
+ @logger.debug("\t#{opt.to_s}:")
16
+ if val.kind_of?(Array)
17
+ val.each do |v|
18
+ @logger.debug("\t\t#{v.to_s}")
19
+ end
20
+ else
21
+ @logger.debug("\t\t#{val.to_s}")
22
+ end
23
+ end
24
+ end
25
+
26
+ @config = Beaker::TestConfig.new(@options[:config], @options)
27
+
28
+ #add additional paths to the LOAD_PATH
29
+ if not @options[:load_path].empty?
30
+ @options[:load_path].each do |path|
31
+ $LOAD_PATH << File.expand_path(path)
32
+ end
33
+ end
34
+ @options[:helper].each do |helper|
35
+ require File.expand_path(helper)
36
+ end
37
+
38
+ @hosts = []
39
+ @network_manager = Beaker::NetworkManager.new(@config, @options, @logger)
40
+ @hosts = @network_manager.provision
41
+ #validate that the hosts are correctly configured
42
+ Beaker::Utils::Validator.validate(@hosts, @logger)
43
+
44
+ end
45
+
46
+ def execute!
47
+ @ntp_controller = Beaker::Utils::NTPControl.new(@options, @hosts)
48
+ @setup = Beaker::Utils::SetupHelper.new(@options, @hosts)
49
+ @repo_controller = Beaker::Utils::RepoControl.new(@options, @hosts)
50
+
51
+ setup_steps = [[:timesync, "Sync time on hosts", Proc.new {@ntp_controller.timesync}],
52
+ [:root_keys, "Sync keys to hosts" , Proc.new {@setup.sync_root_keys}],
53
+ [:repo_proxy, "Proxy packaging repositories on ubuntu, debian and solaris-11", Proc.new {@repo_controller.proxy_config}],
54
+ [:add_el_extras, "Add Extra Packages for Enterprise Linux (EPEL) repository to el-* hosts", Proc.new {@repo_controller.add_el_extras}],
55
+ [:add_master_entry, "Update /etc/hosts on master with master's ip", Proc.new {@setup.add_master_entry}]]
56
+
57
+ begin
58
+ trap(:INT) do
59
+ @logger.warn "Interrupt received; exiting..."
60
+ exit(1)
61
+ end
62
+ #setup phase
63
+ setup_steps.each do |step|
64
+ if (not @options.has_key?(step[0])) or @options[step[0]]
65
+ @logger.notify ""
66
+ @logger.notify "Setup: #{step[1]}"
67
+ step[2].call
68
+ end
69
+ end
70
+
71
+ #pre acceptance phase
72
+ run_suite('pre-suite', @options.merge({:tests => @options[:pre_suite]}), :fail_fast)
73
+ #testing phase
74
+ begin
75
+ run_suite('acceptance', @options)
76
+ #post acceptance phase
77
+ rescue => e
78
+ #post acceptance on failure
79
+ #if we error then run the post suite as long as we aren't in fail-stop mode
80
+ run_suite('post-suite', @options.merge({:tests => @options[:post_suite]})) unless @options[:fail_mode] == "stop"
81
+ raise e
82
+ else
83
+ #post acceptance on success
84
+ run_suite('post-suite', @options.merge({:tests => @options[:post_suite]}))
85
+ end
86
+ #cleanup phase
87
+ rescue => e
88
+ #cleanup on error
89
+ #only do cleanup if we aren't in fail-stop mode
90
+ @logger.notify "Cleanup: cleaning up after failed run"
91
+ if @options[:fail_mode] != "stop"
92
+ @network_manager.cleanup
93
+ end
94
+ raise "Failed to execute tests!"
95
+ else
96
+ #cleanup on success
97
+ @logger.notify "Cleanup: cleaning up after successful run"
98
+ @network_manager.cleanup
99
+ end
100
+ end
101
+
102
+ def run_suite(name, options, failure_strategy = false)
103
+ if (options[:tests].empty?)
104
+ @logger.notify("No tests to run for suite '#{name}'")
105
+ return
106
+ end
107
+ Beaker::TestSuite.new(
108
+ name, @hosts, options, @config, failure_strategy
109
+ ).run_and_raise_on_failure
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,241 @@
1
+ module Beaker
2
+ # An object that represents a "command" on a remote host. Is responsible
3
+ # for munging the environment correctly. Probably poorly named.
4
+ #
5
+ # @api public
6
+ class Command
7
+
8
+ DEFAULT_GIT_RUBYLIB = {
9
+ :default => [],
10
+ :host => %w(hieralibdir hierapuppetlibdir
11
+ pluginlibpath puppetlibdir
12
+ facterlibdir),
13
+ :opts => { :additive => true,
14
+ :separator => {:host => 'pathseparator' }
15
+ }
16
+ }
17
+
18
+ DEFAULT_GIT_PATH = {
19
+ :default => [],
20
+ :host => %w(puppetbindir facterbindir hierabindir),
21
+ :opts => { :additive => true, :separator => ':' }
22
+ }
23
+
24
+ DEFAULT_GIT_ENV = { :PATH => DEFAULT_GIT_PATH, :RUBYLIB => DEFAULT_GIT_RUBYLIB }
25
+
26
+ # A string representing the (possibly) incomplete command
27
+ attr_accessor :command
28
+
29
+ # A hash key-values where the keys are environment variables to be set
30
+ attr_accessor :environment
31
+
32
+ # A hash of options. Keys with values of nil are considered flags
33
+ attr_accessor :options
34
+
35
+ # An array of additional arguments to be supplied to the command
36
+ attr_accessor :args
37
+
38
+ # @param [String] command The program to call, either an absolute path
39
+ # or one in the PATH (can be overridden)
40
+ # @param [Array] args These are addition arguments to the command
41
+ # @param [Hash] options These are addition options to the command. They
42
+ # will be added in "--key=value" after the command
43
+ # but before the arguments. There is a special key,
44
+ # 'ENV', that won't be used as a command option,
45
+ # but instead can be used to set any default
46
+ # environment variables
47
+ #
48
+ # @example Recommended usage programmatically:
49
+ # Command.new('git add', files, :patch => true, 'ENV' => {'PATH' => '/opt/csw/bin'})
50
+ #
51
+ # @example My favorite example of a signature that we must maintain
52
+ # Command.new('puppet', :resource, 'scheduled_task', name,
53
+ # [ 'ensure=present',
54
+ # 'command=c:\\\\windows\\\\system32\\\\notepad2.exe',
55
+ # "arguments=args-#{name}" ] )
56
+ #
57
+ # @note For backwards compatability we must support any number of strings
58
+ # or symbols (or arrays of strings an symbols) and essentially
59
+ # ensure they are in a flattened array, coerced to strings, and
60
+ # call #join(' ') on it. We have options for the command line
61
+ # invocation that must be turned into '--key=value' and similarly
62
+ # joined as well as a hash of environment key value pairs, and
63
+ # finally we need a hash of options to control the default envs that
64
+ # are included.
65
+ def initialize command, args = [], options = {}
66
+ @command = command
67
+ @options = options
68
+ @args = args
69
+ @environment = {}
70
+
71
+ # this is deprecated and will not allow you to use a command line
72
+ # option of `--environment`, please use ENV instead.
73
+ [:ENV, :environment, 'environment', 'ENV'].each do |k|
74
+ if @options[k].is_a?(Hash)
75
+ @environment = @environment.merge(@options.delete(k))
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+ # @param [Host] host An object that implements {Beaker::Host}'s
82
+ # interface.
83
+ # @param [String] cmd An command to call.
84
+ # @param [Hash] env An optional hash of environment variables to be used
85
+ #
86
+ # @return [String] This returns the fully formed command line invocation.
87
+ def cmd_line host, cmd = @command, env = @environment
88
+ env_string = env.nil? ? '' : environment_string_for( host, env )
89
+
90
+ # This will cause things like `puppet -t -v agent` which is maybe bad.
91
+ "#{env_string} #{cmd} #{options_string} #{args_string}"
92
+ end
93
+
94
+ # @param [Hash] options These are the options that the command takes
95
+ #
96
+ # @return [String] String of the options and flags for command.
97
+ #
98
+ # @note Why no. Not the least bit Unixy, why do you ask?
99
+ def options_string opts = @options
100
+ flags = []
101
+ options = opts.dup
102
+ options.each_key do |key|
103
+ if options[key] == nil
104
+ flags << key
105
+ options.delete(key)
106
+ end
107
+ end
108
+
109
+ short_flags, long_flags = flags.partition {|flag| flag.to_s.length == 1 }
110
+ parsed_short_flags = short_flags.map {|f| "-#{f}" }
111
+ parsed_long_flags = long_flags.map {|f| "--#{f}" }
112
+
113
+ short_opts, long_opts = {}, {}
114
+ options.each_key do |key|
115
+ if key.to_s.length == 1
116
+ short_opts[key] = options[key]
117
+ else
118
+ long_opts[key] = options[key]
119
+ end
120
+ end
121
+ parsed_short_opts = short_opts.map {|k,v| "-#{k}=#{v}" }
122
+ parsed_long_opts = long_opts.map {|k,v| "--#{k}=#{v}" }
123
+
124
+ return (parsed_short_flags +
125
+ parsed_long_flags +
126
+ parsed_short_opts + parsed_long_opts).join(' ')
127
+ end
128
+
129
+ # @param [Array] args An array of arguments to the command.
130
+ #
131
+ # @return [String] String of the arguments for command.
132
+ def args_string args = @args
133
+ args.flatten.compact.join(' ')
134
+ end
135
+
136
+ # Determine the appropriate env commands for the given host.
137
+ #
138
+ # @param [Host] host A Host object
139
+ # @param [Hash{String=>String}] env An optional Hash containing
140
+ # key-value pairs to be treated
141
+ # as environment variables that
142
+ # should be set for the duration
143
+ # of the puppet command.
144
+ #
145
+ # @return [String] Returns a string containing command line arguments that
146
+ # will ensure the environment is correctly set for the
147
+ # given host.
148
+ #
149
+ # @note I dislike the principle of this method. There is host specific
150
+ # knowledge contained here. Really the relationship should be
151
+ # reversed where a host is asked for an appropriate Command when
152
+ # given a generic Command.
153
+ def environment_string_for host, env
154
+ return '' if env.empty?
155
+
156
+ env_array = parse_env_hash_for( host, env ).compact
157
+
158
+ # cygwin-ism
159
+ cmd = host['platform'] =~ /windows/ ? 'cmd.exe /c' : nil
160
+ env_array << cmd if cmd
161
+
162
+ environment_string = env_array.join(' ')
163
+
164
+ "env #{environment_string}"
165
+ end
166
+
167
+ # @!visibility private
168
+ def parse_env_hash_for( host, env = @environment )
169
+ # I needlessly love inject
170
+ env.inject([]) do |array_of_parsed_vars, key_and_value|
171
+ variable, val_in_unknown_format = *key_and_value
172
+ if val_in_unknown_format.is_a?(Hash)
173
+ value = val_in_unknown_format
174
+ elsif val_in_unknown_format.is_a?(Array)
175
+ value = { :default => val_in_unknown_format }
176
+ else
177
+ value = { :default => [ val_in_unknown_format.to_s ] }
178
+ end
179
+
180
+ var_settings = ensure_correct_structure_for( value )
181
+ # any default array of variable values ( like [ '/bin', '/usr/bin' ] for PATH )
182
+ default_values = var_settings[:default]
183
+
184
+ # host specific values, ie :host => [ 'puppetlibdir' ] is evaluated to
185
+ # an array with whatever host['puppetlibdir'] is
186
+ host_values = var_settings[:host].map { |attr| host[attr] }
187
+
188
+ # the two arrays are combined with host specific values first
189
+ var_array = ( host_values + default_values ).compact
190
+
191
+ # This will add the name of the variable, so :PATH => { ... }
192
+ # gets '${PATH}' appended to it if the :additive opt is passed
193
+ var_array << "${#{variable}}" if var_settings[:opts][:additive]
194
+
195
+ # This is stupid, but because we're using cygwin we sometimes need to use
196
+ # ':' and sometimes ';' on windows as a separator
197
+ attr_string = join_env_vars_for( var_array, host, var_settings[:opts][:separator] )
198
+ var_string = attr_string.empty? ? nil : %Q[#{variable}="#{attr_string}"]
199
+
200
+ # Now we append this to our accumulator array ie [ 'RUBYLIB=....', 'PATH=....' ]
201
+ array_of_parsed_vars << var_string
202
+
203
+ array_of_parsed_vars
204
+ end
205
+ end
206
+
207
+ # @!visibility private
208
+ def ensure_correct_structure_for( settings )
209
+ structure = { :default => [],
210
+ :host => [],
211
+ :opts => {}
212
+ }.merge( settings )
213
+ structure[:opts][:separator] ||= ':'
214
+ structure
215
+ end
216
+
217
+ # @!visibility private
218
+ def join_env_vars_for( array_of_variables, host, separator = ':' )
219
+ if separator.is_a?( Hash )
220
+ separator = host[separator[:host]]
221
+ end
222
+ array_of_variables.join( separator )
223
+ end
224
+ end
225
+
226
+ class PuppetCommand < Command
227
+ def initialize *args
228
+ command = "puppet #{args.shift}"
229
+ opts = args.last.is_a?(Hash) ? args.pop : Hash.new
230
+ opts['ENV'] ||= Hash.new
231
+ opts['ENV'] = opts['ENV'].merge( DEFAULT_GIT_ENV )
232
+ super( command, args, opts )
233
+ end
234
+ end
235
+
236
+ class HostCommand < Command
237
+ def cmd_line host
238
+ eval "\"#{@command}\""
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,21 @@
1
+ require 'test/unit/assertions'
2
+
3
+ module Beaker
4
+ module CommandFactory
5
+ include Test::Unit::Assertions
6
+
7
+ def execute(command, options={}, &block)
8
+ result = self.exec(Command.new(command), options)
9
+
10
+ if block_given?
11
+ yield result
12
+ else
13
+ result.stdout.chomp
14
+ end
15
+ end
16
+
17
+ def fail_test(msg)
18
+ assert(false, msg)
19
+ end
20
+ end
21
+ end
data/lib/beaker/dsl.rb ADDED
@@ -0,0 +1,85 @@
1
+ [ 'install_utils', 'roles', 'outcomes', 'assertions',
2
+ 'structure', 'helpers', 'wrappers' ].each do |file|
3
+ begin
4
+ require "beaker/dsl/#{file}"
5
+ rescue LoadError
6
+ require File.expand_path(File.join(File.dirname(__FILE__), 'dsl', file))
7
+ end
8
+ end
9
+
10
+ module Beaker
11
+ # This is a catch all module for including Puppetlabs home grown testing
12
+ # DSL. This module is mixed into {Beaker::TestCase} and can be
13
+ # mixed into any test runner by defining the methods that it requires to
14
+ # interact with. If not all of the functionality is required sub modules of
15
+ # the DSL may be mixed into a test runner of your choice.
16
+ #
17
+ # Currently most DSL modules require #logger and #hosts defined. #logger
18
+ # should provided the methods #debug, #warn and #notify and may be a
19
+ # wrapper to any logger you wish (or {Beaker::Logger}). #hosts
20
+ # should return an array of objects which conform to the interface defined
21
+ # in {Beaker::Host} (primarily it should provide Hash like access
22
+ # and interfaces like {Beaker::Host#exec},
23
+ # {Beaker::Host#do_scp_to}, and
24
+ # {Beaker::Host#do_scp_from}.
25
+ #
26
+ #
27
+ # @example Writing a complete testcase to be ran by the builtin test runner.
28
+ # test_name 'Ensure My App Starts Correctly' do
29
+ # confine :except, :platform => ['windows', 'solaris']
30
+ #
31
+ # teardown do
32
+ # on master, puppet('resource mything ensure=absent')
33
+ # on agents, 'kill -9 allTheThings'
34
+ # end
35
+ #
36
+ # step 'Ensure Pre-Requisites are Installed' do
37
+ # end
38
+ #
39
+ # with_puppet_running_on master, :master, :logdest => '/tmp/blah' do
40
+ #
41
+ # step 'Run Startup Script' do
42
+ # end
43
+ #
44
+ # step 'And... Did it work?' do
45
+ # end
46
+ # end
47
+ # end
48
+ #
49
+ # @example Writing an Example to be ran within RSpec
50
+ # #=> spec_helper.rb
51
+ # RSpec.configure do |c|
52
+ # c.include 'beaker/dsl/helpers'
53
+ # c.include 'beaker/dsl/rspec/matchers'
54
+ # c.include 'beaker/dsl/rspec/expectations'
55
+ # c.include 'beaker/host'
56
+ # end
57
+ #
58
+ # #=> my_acceptance_spec.rb
59
+ # require 'spec_helper'
60
+ #
61
+ # describe 'A Test With RSpec' do
62
+ # let(:hosts) { Host.new('blah', 'blah', 'not helpful' }
63
+ # let(:logger) { Where.is('the', 'rspec', 'logger') }
64
+ #
65
+ # after do
66
+ # on master, puppet('resource mything ensure=absent')
67
+ # on agents, 'kill -9 allTheThings'
68
+ # end
69
+ #
70
+ # it 'tests stuff?' do
71
+ # result = on( hosts.first, 'ls ~' )
72
+ # expect( result.stdout ).to match /my_file/
73
+ # end
74
+ # end
75
+ #
76
+ module DSL
77
+ include Beaker::DSL::Roles
78
+ include Beaker::DSL::Outcomes
79
+ include Beaker::DSL::Structure
80
+ include Beaker::DSL::Assertions
81
+ include Beaker::DSL::Wrappers
82
+ include Beaker::DSL::Helpers
83
+ include Beaker::DSL::InstallUtils
84
+ end
85
+ end