beaker 0.0.0

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