beaker 0.0.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.travis.yml +8 -0
- data/README.md +6 -6
- data/beaker.gemspec +6 -2
- data/lib/beaker.rb +1 -1
- data/lib/beaker/answers.rb +34 -7
- data/lib/beaker/answers/version20.rb +124 -0
- data/lib/beaker/answers/version28.rb +21 -0
- data/lib/beaker/answers/version30.rb +24 -5
- data/lib/beaker/cli.rb +55 -41
- data/lib/beaker/command.rb +2 -2
- data/lib/beaker/dsl/helpers.rb +320 -106
- data/lib/beaker/dsl/install_utils.rb +202 -81
- data/lib/beaker/dsl/roles.rb +40 -0
- data/lib/beaker/host.rb +28 -20
- data/lib/beaker/host/unix.rb +7 -4
- data/lib/beaker/host/unix/pkg.rb +42 -12
- data/lib/beaker/host/windows.rb +9 -5
- data/lib/beaker/host/windows/group.rb +1 -1
- data/lib/beaker/host/windows/pkg.rb +41 -8
- data/lib/beaker/hypervisor.rb +23 -10
- data/lib/beaker/hypervisor/aixer.rb +15 -19
- data/lib/beaker/hypervisor/blimper.rb +71 -72
- data/lib/beaker/hypervisor/fusion.rb +11 -10
- data/lib/beaker/hypervisor/solaris.rb +17 -23
- data/lib/beaker/hypervisor/vagrant.rb +27 -12
- data/lib/beaker/hypervisor/vcloud.rb +154 -138
- data/lib/beaker/hypervisor/vcloud_pooled.rb +97 -0
- data/lib/beaker/hypervisor/vsphere.rb +8 -5
- data/lib/beaker/hypervisor/vsphere_helper.rb +43 -33
- data/lib/beaker/network_manager.rb +16 -12
- data/lib/beaker/options/command_line_parser.rb +199 -0
- data/lib/beaker/options/hosts_file_parser.rb +39 -0
- data/lib/beaker/options/options_file_parser.rb +45 -0
- data/lib/beaker/options/options_hash.rb +294 -0
- data/lib/beaker/options/parser.rb +288 -0
- data/lib/beaker/options/pe_version_scraper.rb +35 -0
- data/lib/beaker/options/presets.rb +70 -0
- data/lib/beaker/shared.rb +2 -1
- data/lib/beaker/shared/host_handler.rb +7 -2
- data/lib/beaker/shared/repetition.rb +1 -0
- data/lib/beaker/shared/timed.rb +14 -0
- data/lib/beaker/test_case.rb +2 -38
- data/lib/beaker/test_suite.rb +11 -25
- data/lib/beaker/utils/repo_control.rb +6 -8
- data/lib/beaker/utils/setup_helper.rb +9 -20
- data/spec/beaker/answers_spec.rb +109 -0
- data/spec/beaker/command_spec.rb +2 -2
- data/spec/beaker/dsl/assertions_spec.rb +1 -3
- data/spec/beaker/dsl/helpers_spec.rb +519 -84
- data/spec/beaker/dsl/install_utils_spec.rb +265 -16
- data/spec/beaker/dsl/roles_spec.rb +31 -10
- data/spec/beaker/host/windows/group_spec.rb +55 -0
- data/spec/beaker/host_spec.rb +130 -40
- data/spec/beaker/hypervisor/aixer_spec.rb +34 -0
- data/spec/beaker/hypervisor/blimper_spec.rb +77 -0
- data/spec/beaker/hypervisor/fusion_spec.rb +26 -0
- data/spec/beaker/hypervisor/hypervisor_spec.rb +66 -0
- data/spec/beaker/hypervisor/solaris_spec.rb +39 -0
- data/spec/beaker/hypervisor/vagrant_spec.rb +105 -0
- data/spec/beaker/hypervisor/vcloud_pooled_spec.rb +60 -0
- data/spec/beaker/hypervisor/vcloud_spec.rb +70 -0
- data/spec/beaker/hypervisor/vsphere_helper_spec.rb +162 -0
- data/spec/beaker/hypervisor/vsphere_spec.rb +76 -0
- data/spec/beaker/options/command_line_parser_spec.rb +25 -0
- data/spec/beaker/options/data/LATEST +1 -0
- data/spec/beaker/options/data/badyaml.cfg +21 -0
- data/spec/beaker/options/data/hosts.cfg +21 -0
- data/spec/beaker/options/data/opts.txt +6 -0
- data/spec/beaker/options/hosts_file_parser_spec.rb +30 -0
- data/spec/beaker/options/options_file_parser_spec.rb +23 -0
- data/spec/beaker/options/options_hash_spec.rb +111 -0
- data/spec/beaker/options/parser_spec.rb +172 -0
- data/spec/beaker/options/pe_version_scaper_spec.rb +15 -0
- data/spec/beaker/options/presets_spec.rb +24 -0
- data/spec/beaker/puppet_command_spec.rb +54 -21
- data/spec/beaker/shared/error_handler_spec.rb +40 -0
- data/spec/beaker/shared/host_handler_spec.rb +104 -0
- data/spec/beaker/shared/repetition_spec.rb +72 -0
- data/spec/beaker/test_suite_spec.rb +3 -16
- data/spec/beaker/utils/ntp_control_spec.rb +42 -0
- data/spec/beaker/utils/repo_control_spec.rb +168 -0
- data/spec/beaker/utils/setup_helper_spec.rb +82 -0
- data/spec/beaker/utils/validator_spec.rb +58 -0
- data/spec/helpers.rb +97 -0
- data/spec/matchers.rb +39 -0
- data/spec/mock_blimpy.rb +48 -0
- data/spec/mock_fission.rb +60 -0
- data/spec/mock_vsphere.rb +310 -0
- data/spec/mock_vsphere_helper.rb +183 -0
- data/spec/mocks.rb +83 -0
- data/spec/spec_helper.rb +8 -1
- metadata +106 -13
- data/beaker.rb +0 -10
- data/lib/beaker/options_parsing.rb +0 -323
- data/lib/beaker/test_config.rb +0 -148
- data/spec/beaker/options_parsing_spec.rb +0 -37
- data/spec/mocks_and_helpers.rb +0 -34
@@ -0,0 +1,39 @@
|
|
1
|
+
module Beaker
|
2
|
+
module Options
|
3
|
+
#A set of functions to parse hosts files
|
4
|
+
module HostsFileParser
|
5
|
+
|
6
|
+
# Read the contents of the hosts.cfg into an OptionsHash, merge the 'CONFIG' section into the OptionsHash, return OptionsHash
|
7
|
+
# @param [String] hosts_file_path The path to the hosts file
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# hosts_hash = HostsFileParser.parse_hosts_file('sample.cfg')
|
11
|
+
# hosts_hash == {:HOSTS=>{:"pe-ubuntu-lucid"=>{:roles=>["agent", "dashboard", "database", "master"], ... }
|
12
|
+
#
|
13
|
+
# @return [OptionsHash] The contents of the hosts file as an OptionsHash
|
14
|
+
# @raise [ArgumentError] Raises if hosts_file_path is not a path to a file, or is not a valid YAML file
|
15
|
+
def self.parse_hosts_file(hosts_file_path)
|
16
|
+
hosts_file_path = File.expand_path(hosts_file_path)
|
17
|
+
unless File.exists?(hosts_file_path)
|
18
|
+
raise ArgumentError, "Required host file '#{hosts_file_path}' does not exist!"
|
19
|
+
end
|
20
|
+
host_options = Beaker::Options::OptionsHash.new
|
21
|
+
begin
|
22
|
+
host_options = host_options.merge(YAML.load_file(hosts_file_path))
|
23
|
+
rescue Psych::SyntaxError => e
|
24
|
+
raise ArgumentError, "#{hosts_file_path} is not a valid YAML file\n\t#{e}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Make sure the roles array is present for all hosts
|
28
|
+
host_options['HOSTS'].each_key do |host|
|
29
|
+
host_options['HOSTS'][host]['roles'] ||= []
|
30
|
+
end
|
31
|
+
if host_options.has_key?('CONFIG')
|
32
|
+
host_options = host_options.merge(host_options.delete('CONFIG'))
|
33
|
+
end
|
34
|
+
host_options
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
module Beaker
|
3
|
+
module Options
|
4
|
+
#A set of functions to read options files
|
5
|
+
module OptionsFileParser
|
6
|
+
|
7
|
+
# Eval the contents of options_file_path, return as an OptionsHash
|
8
|
+
#
|
9
|
+
# Options file is assumed to contain extra options stored in a Hash
|
10
|
+
#
|
11
|
+
# ie,
|
12
|
+
# {
|
13
|
+
# :debug => true,
|
14
|
+
# :tests => "test.rb",
|
15
|
+
# }
|
16
|
+
#
|
17
|
+
# @param [String] options_file_path The path to the options file
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# options_hash = OptionsFileParser.parse_options_file('sample.cfg')
|
21
|
+
# options_hash == {:debug=>true, :tests=>"test.rb", :pre_suite=>["pre-suite.rb"], :post_suite=>"post_suite1.rb,post_suite2.rb"}
|
22
|
+
#
|
23
|
+
# @return [OptionsHash] The contents of the options file as an OptionsHash
|
24
|
+
# @raise [ArgumentError] Raises if options_file_path is not a path to a file
|
25
|
+
# @note Since the options_file is Eval'ed, any other Ruby commands will also be executed, this can be used
|
26
|
+
# to set additional environment variables
|
27
|
+
def self.parse_options_file(options_file_path)
|
28
|
+
result = Beaker::Options::OptionsHash.new
|
29
|
+
if options_file_path
|
30
|
+
options_file_path = File.expand_path(options_file_path)
|
31
|
+
unless File.exists?(options_file_path)
|
32
|
+
raise ArgumentError, "Specified options file '#{options_file_path}' does not exist!"
|
33
|
+
end
|
34
|
+
# This eval will allow the specified options file to have access to our
|
35
|
+
# scope. It is important that the variable 'options_file_path' is
|
36
|
+
# accessible, because some existing options files (e.g. puppetdb) rely on
|
37
|
+
# that variable to determine their own location (for use in 'require's, etc.)
|
38
|
+
result = result.merge(eval(File.read(options_file_path)))
|
39
|
+
end
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,294 @@
|
|
1
|
+
module Beaker
|
2
|
+
module Options
|
3
|
+
|
4
|
+
# A hash that treats Symbol and String keys interchangeably
|
5
|
+
# and recursively merges hashes
|
6
|
+
class OptionsHash < Hash
|
7
|
+
|
8
|
+
# The dividor between elements when OptionsHash is dumped
|
9
|
+
DIV = ' '
|
10
|
+
|
11
|
+
# The end of line when dumping
|
12
|
+
EOL = "\n"
|
13
|
+
|
14
|
+
# Get value for given key, search for both k as String and k as Symbol,
|
15
|
+
# if not present return nil
|
16
|
+
#
|
17
|
+
# @param [Object] k The key to find, searches for both k as String
|
18
|
+
# and k as Symbol
|
19
|
+
#
|
20
|
+
# @example Use this method to return the value for a given key
|
21
|
+
# a['key'] = 'value'
|
22
|
+
# a['key'] == a[:key] == 'value'
|
23
|
+
#
|
24
|
+
# @return [nil, Object] Return the Object found at given key,
|
25
|
+
# or nil if no Object found
|
26
|
+
def [] k
|
27
|
+
super(k.to_s) || super(k.to_sym)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Set Symbol key to Object value
|
31
|
+
# @param [Object] k The key to associated with the value,
|
32
|
+
# converted to Symbol key
|
33
|
+
# @param [Object] v The value to store in the ObjectHash
|
34
|
+
#
|
35
|
+
# @example Use this method to set the value for a key
|
36
|
+
# a['key'] = 'value'
|
37
|
+
#
|
38
|
+
# @return [Object] Return the Object value just stored
|
39
|
+
def []=k,v
|
40
|
+
super(k.to_sym, v)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Determine if type of ObjectHash is pe, defaults to true
|
44
|
+
#
|
45
|
+
# @example Use this method to return the value for a given key
|
46
|
+
# a['type'] = 'pe'
|
47
|
+
# a.is_pe? == true
|
48
|
+
#
|
49
|
+
# @return [Boolean]
|
50
|
+
def is_pe?
|
51
|
+
self[:type] ? self[:type] =~ /pe/ : true
|
52
|
+
end
|
53
|
+
|
54
|
+
# Determine if key is stored in ObjectHash
|
55
|
+
# @param [Object] k The key to find in ObjectHash, searches for
|
56
|
+
# both k as String and k as Symbol
|
57
|
+
#
|
58
|
+
# @example Use this method to set the value for a key
|
59
|
+
# a['key'] = 'value'
|
60
|
+
# a.has_key[:key] == true
|
61
|
+
#
|
62
|
+
# @return [Boolean]
|
63
|
+
def has_key? k
|
64
|
+
super(k.to_s) || super(k.to_sym)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Determine key=>value entry in OptionsHash, remove both value at
|
68
|
+
# String key and value at Symbol key
|
69
|
+
#
|
70
|
+
# @param [Object] k The key to delete in ObjectHash,
|
71
|
+
# deletes both k as String and k as Symbol
|
72
|
+
#
|
73
|
+
# @example Use this method to set the value for a key
|
74
|
+
# a['key'] = 'value'
|
75
|
+
# a.delete[:key] == 'value'
|
76
|
+
#
|
77
|
+
# @return [Object, nil] The Object deleted at value,
|
78
|
+
# nil if no Object deleted
|
79
|
+
def delete k
|
80
|
+
super(k.to_s) || super(k.to_sym)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Recursively merge and OptionsHash with an OptionsHash or Hash
|
84
|
+
#
|
85
|
+
# @param [OptionsHash] base The hash to merge into
|
86
|
+
# @param [OptionsHash, Hash] hash The hash to merge from
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# base = { :key => { :subkey1 => 'subval', :subkey2 => 'subval' } }
|
90
|
+
# hash = { :key => { :subkey1 => 'newval'} }
|
91
|
+
#
|
92
|
+
# rmerge(base, hash)
|
93
|
+
# #=> {:key =>
|
94
|
+
# {:subkey1 => 'newval',
|
95
|
+
# :subkey2 => 'subval'}}
|
96
|
+
#
|
97
|
+
# @return [OptionsHash] The combined bash and hash
|
98
|
+
def rmerge base, hash
|
99
|
+
return base unless hash.is_a?(Hash) || hash.is_a?(OptionsHash)
|
100
|
+
hash.each do |key, v|
|
101
|
+
if (base[key].is_a?(Hash) || base[key].is_a?(OptionsHash)) && (hash[key].is_a?(Hash) || has[key].is_a?(OptionsHash))
|
102
|
+
rmerge(base[key], hash[key])
|
103
|
+
elsif hash[key].is_a?(Hash)
|
104
|
+
base[key] = OptionsHash.new.merge(hash[key])
|
105
|
+
else
|
106
|
+
base[key]= hash[key]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
base
|
110
|
+
end
|
111
|
+
|
112
|
+
# Recursively merge self with an OptionsHash or Hash
|
113
|
+
#
|
114
|
+
# @param [OptionsHash, Hash] hash The hash to merge from
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# base = { :key => { :subkey1 => 'subval', :subkey2 => 'subval' } }
|
118
|
+
# hash = { :key => { :subkey1 => 'newval'} }
|
119
|
+
#
|
120
|
+
# base.merge(hash)
|
121
|
+
# #=> {:key =>
|
122
|
+
# {:subkey1 => 'newval',
|
123
|
+
# :subkey2 => 'subval' }
|
124
|
+
#
|
125
|
+
# @return [OptionsHash] The combined hash
|
126
|
+
def merge hash
|
127
|
+
rmerge(self, hash)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Helper for formatting collections
|
131
|
+
# Computes the indentation level for elements of the collection
|
132
|
+
# Yields indentation to block to so the caller can create
|
133
|
+
# map of element strings
|
134
|
+
# Places delimiters in the correct location
|
135
|
+
# Joins everything with correct EOL
|
136
|
+
#
|
137
|
+
#
|
138
|
+
# !@visibility private
|
139
|
+
def as_coll( opening, closing, in_lvl, in_inc, &block )
|
140
|
+
delim_indent = in_inc * in_lvl
|
141
|
+
elem_indent = in_inc * (in_lvl + 1)
|
142
|
+
|
143
|
+
open_brace = opening
|
144
|
+
close_brace = delim_indent + closing
|
145
|
+
|
146
|
+
fmtd_coll = block.call( elem_indent )
|
147
|
+
str_coll = fmtd_coll.join( ',' + EOL )
|
148
|
+
|
149
|
+
return open_brace + EOL + str_coll + EOL + close_brace
|
150
|
+
end
|
151
|
+
|
152
|
+
# Pretty prints a collection
|
153
|
+
#
|
154
|
+
# @param [Enumerable] collection The collection to be printed
|
155
|
+
# @param [Integer] in_lvl The level of indentation
|
156
|
+
# @param [String] in_inc The increment to indent
|
157
|
+
#
|
158
|
+
# @example
|
159
|
+
# base = {:key => { :subkey1 => 'subval', :subkey2 => ['subval'] }}
|
160
|
+
# self.fmt_collection( base )
|
161
|
+
# #=> '{
|
162
|
+
# "key": {
|
163
|
+
# "subkey": "subval",
|
164
|
+
# "subkey2": [
|
165
|
+
# "subval"
|
166
|
+
# ]
|
167
|
+
# }
|
168
|
+
# }'
|
169
|
+
#
|
170
|
+
# @return [String] The collection as a pretty JSON object
|
171
|
+
def fmt_collection( collection, in_lvl = 0, in_inc = DIV )
|
172
|
+
if collection.respond_to? :each_pair
|
173
|
+
string = fmt_assoc( collection, in_lvl, in_inc )
|
174
|
+
else
|
175
|
+
string = fmt_list( collection, in_lvl, in_inc )
|
176
|
+
end
|
177
|
+
|
178
|
+
return string
|
179
|
+
end
|
180
|
+
|
181
|
+
# Pretty prints an associative collection
|
182
|
+
#
|
183
|
+
# @param [#each_pair] coll The collection to be printed
|
184
|
+
# @param [Integer] in_lvl The level of indentation
|
185
|
+
# @param [String] in_inc The increment to indent
|
186
|
+
#
|
187
|
+
# @example
|
188
|
+
# base = { :key => 'value', :key2 => 'value' }
|
189
|
+
# self.fmt_assoc( base )
|
190
|
+
# #=> '{
|
191
|
+
# "key": "value",
|
192
|
+
# "key2": "value"
|
193
|
+
# }'
|
194
|
+
#
|
195
|
+
# @return [String] The collection as a pretty JSON object
|
196
|
+
def fmt_assoc( coll, in_lvl = 0, in_inc = DIV )
|
197
|
+
if coll.empty?
|
198
|
+
return '{}'
|
199
|
+
else
|
200
|
+
as_coll '{', '}', in_lvl, in_inc do |elem_indent|
|
201
|
+
coll.map do |key, value|
|
202
|
+
assoc_line = elem_indent + '"' + key.to_s + '"' + ': '
|
203
|
+
assoc_line += fmt_value( value, in_lvl, in_inc )
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Pretty prints a list collection
|
210
|
+
#
|
211
|
+
# @param [#each] coll The collection to be printed
|
212
|
+
# @param [Integer] in_lvl The level of indentation
|
213
|
+
# @param [String] in_inc The increment to indent
|
214
|
+
#
|
215
|
+
# @example
|
216
|
+
# base = [ 'first', 'second' ]
|
217
|
+
# self.fmt_list( base )
|
218
|
+
# #=> '[
|
219
|
+
# "first",
|
220
|
+
# "second"
|
221
|
+
# ]'
|
222
|
+
#
|
223
|
+
# @return [String] The collection as a pretty JSON object
|
224
|
+
def fmt_list( coll, in_lvl = 0, in_inc = DIV )
|
225
|
+
if coll.empty?
|
226
|
+
return '[]'
|
227
|
+
else
|
228
|
+
as_coll '[', ']', in_lvl, in_inc do |indent|
|
229
|
+
coll.map do |el|
|
230
|
+
indent + fmt_value( el, in_lvl, in_inc )
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Chooses between collection and primitive formatting
|
237
|
+
#
|
238
|
+
# !@visibility private
|
239
|
+
def fmt_value( value, in_lvl = 0, in_inc = DIV )
|
240
|
+
if value.kind_of? Enumerable and not value.is_a? String
|
241
|
+
fmt_collection( value, in_lvl + 1, in_inc )
|
242
|
+
else
|
243
|
+
fmt_basic( value )
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Pretty prints primitive JSON values
|
248
|
+
#
|
249
|
+
# @param [Object] value The collection to be printed
|
250
|
+
#
|
251
|
+
# @example
|
252
|
+
# self.fmt_value( 4 )
|
253
|
+
# #=> '4'
|
254
|
+
#
|
255
|
+
# @example
|
256
|
+
# self.fmt_value( true )
|
257
|
+
# #=> 'true'
|
258
|
+
#
|
259
|
+
# @example
|
260
|
+
# self.fmt_value( nil )
|
261
|
+
# #=> 'null'
|
262
|
+
#
|
263
|
+
# @example
|
264
|
+
# self.fmt_value( 'string' )
|
265
|
+
# #=> '"string"'
|
266
|
+
#
|
267
|
+
# @return [String] The value as a valid JSON primitive
|
268
|
+
def fmt_basic( value )
|
269
|
+
case value
|
270
|
+
when Numeric, TrueClass, FalseClass then value.to_s
|
271
|
+
when NilClass then "null"
|
272
|
+
else "\"#{value}\""
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Pretty print the options as JSON
|
277
|
+
#
|
278
|
+
# @example
|
279
|
+
# base = { :key => { :subkey1 => 'subval', :subkey2 => 'subval' } }
|
280
|
+
# base.dump
|
281
|
+
# #=> '{
|
282
|
+
# "key": {
|
283
|
+
# "subkey1": "subval",
|
284
|
+
# "subkey2": 2
|
285
|
+
# }
|
286
|
+
# }
|
287
|
+
#
|
288
|
+
# @return [String] The description of self
|
289
|
+
def dump
|
290
|
+
fmt_collection( self, 0, DIV )
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Beaker
|
4
|
+
module Options
|
5
|
+
#An Object that parses, merges and normalizes all supported Beaker options and arguments
|
6
|
+
class Parser
|
7
|
+
GITREPO = 'git://github.com/puppetlabs'
|
8
|
+
#These options can have the form of arg1,arg2 or [arg] or just arg,
|
9
|
+
#should default to []
|
10
|
+
LONG_OPTS = [:helper, :load_path, :tests, :pre_suite, :post_suite, :install, :modules]
|
11
|
+
#These options expand out into an array of .rb files
|
12
|
+
RB_FILE_OPTS = [:tests, :pre_suite, :post_suite]
|
13
|
+
|
14
|
+
PARSE_ERROR = if RUBY_VERSION > '1.8.7'; then Psych::SyntaxError; else ArgumentError; end
|
15
|
+
|
16
|
+
#The OptionsHash of all parsed options
|
17
|
+
attr_accessor :options
|
18
|
+
|
19
|
+
# Raises an ArgumentError with associated message
|
20
|
+
# @param [String] msg The error message to be reported
|
21
|
+
# @raise [ArgumentError] Takes the supplied message and raises it as an ArgumentError
|
22
|
+
def parser_error msg = ""
|
23
|
+
raise ArgumentError, msg.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the git repository used for git installations
|
27
|
+
# @return [String] The git repository
|
28
|
+
def repo
|
29
|
+
GITREPO
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a description of Beaker's supported arguments
|
33
|
+
# @return [String] The usage String
|
34
|
+
def usage
|
35
|
+
@command_line_parser.usage
|
36
|
+
end
|
37
|
+
|
38
|
+
# Normalizes argument into an Array. Argument can either be converted into an array of a single value,
|
39
|
+
# or can become an array of multiple values by splitting arg over ','. If argument is already an
|
40
|
+
# array that array is returned untouched.
|
41
|
+
# @example
|
42
|
+
# split_arg([1, 2, 3]) == [1, 2, 3]
|
43
|
+
# split_arg(1) == [1]
|
44
|
+
# split_arg("1,2") == ["1", "2"]
|
45
|
+
# split_arg(nil) == []
|
46
|
+
# @param [Array, String] arg Either an array or a string to be split into an array
|
47
|
+
# @return [Array] An array of the form arg, [arg], or arg.split(',')
|
48
|
+
def split_arg arg
|
49
|
+
arry = []
|
50
|
+
if arg.is_a?(Array)
|
51
|
+
arry += arg
|
52
|
+
elsif arg =~ /,/
|
53
|
+
arry += arg.split(',')
|
54
|
+
else
|
55
|
+
arry << arg
|
56
|
+
end
|
57
|
+
arry
|
58
|
+
end
|
59
|
+
|
60
|
+
# Generates a list of files based upon a given path or list of paths.
|
61
|
+
#
|
62
|
+
# Looks recursively for .rb files in paths.
|
63
|
+
#
|
64
|
+
# @param [Array] paths Array of file paths to search for .rb files
|
65
|
+
# @return [Array] An Array of fully qualified paths to .rb files
|
66
|
+
# @raise [ArgumentError] Raises if no .rb files are found in searched directory or if
|
67
|
+
# no .rb files are found overall
|
68
|
+
def file_list(paths)
|
69
|
+
files = []
|
70
|
+
if not paths.empty?
|
71
|
+
paths.each do |root|
|
72
|
+
if File.file? root then
|
73
|
+
files << root
|
74
|
+
else
|
75
|
+
discover_files = Dir.glob(
|
76
|
+
File.join(root, "**/*.rb")
|
77
|
+
).select { |f| File.file?(f) }
|
78
|
+
if discover_files.empty?
|
79
|
+
parser_error "empty directory used as an option (#{root})!"
|
80
|
+
end
|
81
|
+
files += discover_files
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
if files.empty?
|
86
|
+
parser_error "no .rb files found in #{paths.to_s}"
|
87
|
+
end
|
88
|
+
files
|
89
|
+
end
|
90
|
+
|
91
|
+
#Converts array of paths into array of fully qualified git repo URLS with expanded keywords
|
92
|
+
#
|
93
|
+
#Supports the following keywords
|
94
|
+
# PUPPET
|
95
|
+
# FACTER
|
96
|
+
# HIERA
|
97
|
+
# HIERA-PUPPET
|
98
|
+
#@example
|
99
|
+
# opts = ["PUPPET/3.1"]
|
100
|
+
# parse_git_repos(opts) == ["#{GITREPO}/puppet.git#3.1"]
|
101
|
+
#@param [Array] git_opts An array of paths
|
102
|
+
#@return [Array] An array of fully qualified git repo URLs with expanded keywords
|
103
|
+
def parse_git_repos(git_opts)
|
104
|
+
git_opts.map! { |opt|
|
105
|
+
case opt
|
106
|
+
when /^PUPPET\//
|
107
|
+
opt = "#{GITREPO}/puppet.git##{opt.split('/', 2)[1]}"
|
108
|
+
when /^FACTER\//
|
109
|
+
opt = "#{GITREPO}/facter.git##{opt.split('/', 2)[1]}"
|
110
|
+
when /^HIERA\//
|
111
|
+
opt = "#{GITREPO}/hiera.git##{opt.split('/', 2)[1]}"
|
112
|
+
when /^HIERA-PUPPET\//
|
113
|
+
opt = "#{GITREPO}/hiera-puppet.git##{opt.split('/', 2)[1]}"
|
114
|
+
end
|
115
|
+
opt
|
116
|
+
}
|
117
|
+
git_opts
|
118
|
+
end
|
119
|
+
|
120
|
+
#Constructor for Parser
|
121
|
+
#
|
122
|
+
def initialize
|
123
|
+
@command_line_parser = Beaker::Options::CommandLineParser.new
|
124
|
+
end
|
125
|
+
|
126
|
+
# Parses ARGV or provided arguments array, file options, hosts options and combines with environment variables and
|
127
|
+
# preset defaults to generate a Hash representing the Beaker options for a given test run
|
128
|
+
#
|
129
|
+
# Order of priority is as follows:
|
130
|
+
# 1. environment variables are given top priority
|
131
|
+
# 2. host file options
|
132
|
+
# 3. the 'CONFIG' section of the hosts file
|
133
|
+
# 4. ARGV or provided arguments array
|
134
|
+
# 5. options file values
|
135
|
+
# 6. default or preset values are given the lowest priority
|
136
|
+
#
|
137
|
+
# @param [Array] args ARGV or a provided arguments array
|
138
|
+
# @raise [ArgumentError] Raises error on bad input
|
139
|
+
def parse_args(args = ARGV)
|
140
|
+
#NOTE on argument precedence:
|
141
|
+
#
|
142
|
+
# Will use env, then hosts/config file, then command line, then file options
|
143
|
+
#
|
144
|
+
@options = Beaker::Options::Presets.presets
|
145
|
+
cmd_line_options = @command_line_parser.parse!(args)
|
146
|
+
file_options = Beaker::Options::OptionsFileParser.parse_options_file(cmd_line_options[:options_file])
|
147
|
+
# merge together command line and file_options
|
148
|
+
# overwrite file options with command line options
|
149
|
+
cmd_line_and_file_options = file_options.merge(cmd_line_options)
|
150
|
+
# merge command line and file options with defaults
|
151
|
+
# overwrite defaults with command line and file options
|
152
|
+
@options = @options.merge(cmd_line_and_file_options)
|
153
|
+
|
154
|
+
if not @options[:help]
|
155
|
+
#read the hosts file that contains the node configuration and hypervisor info
|
156
|
+
hosts_options = Beaker::Options::HostsFileParser.parse_hosts_file(@options[:hosts_file])
|
157
|
+
# merge in host file vars
|
158
|
+
# overwrite options (default, file options, command line, env) with host file options
|
159
|
+
@options = @options.merge(hosts_options)
|
160
|
+
# merge in env vars
|
161
|
+
# overwrite options (default, file options, command line, hosts file) with env
|
162
|
+
env_vars = Beaker::Options::Presets.env_vars
|
163
|
+
@options = @options.merge(env_vars)
|
164
|
+
|
165
|
+
normalize_args
|
166
|
+
end
|
167
|
+
|
168
|
+
@options
|
169
|
+
end
|
170
|
+
|
171
|
+
# Determine is a given file exists and is a valid YAML file
|
172
|
+
# @param [String] f The YAML file path to examine
|
173
|
+
# @param [String] msg An options message to report in case of error
|
174
|
+
# @raise [ArgumentError] Raise if file does not exist or is not valid YAML
|
175
|
+
def check_yaml_file(f, msg = "")
|
176
|
+
if not File.file?(f)
|
177
|
+
parser_error "#{f} does not exist (#{msg})"
|
178
|
+
end
|
179
|
+
begin
|
180
|
+
YAML.load_file(f)
|
181
|
+
rescue PARSE_ERROR => e
|
182
|
+
parser_error "#{f} is not a valid YAML file (#{msg})\n\t#{e}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
#Validate all merged options values for correctness
|
187
|
+
#
|
188
|
+
#Currently checks:
|
189
|
+
# - if a keyfile is provided then use it
|
190
|
+
# - paths provided to --test, --pre-suite, --post-suite provided lists of .rb files for testing
|
191
|
+
# - --type is one of 'pe' or 'git'
|
192
|
+
# - --fail-mode is one of 'fast', 'stop' or nil
|
193
|
+
# - if using blimpy hypervisor an EC2 YAML file exists
|
194
|
+
# - if using the aix, solaris, or vcloud hypervisors a .fog file exists
|
195
|
+
# - that one and only one master is defined per set of hosts
|
196
|
+
# - that solaris/windows/aix hosts are agent only for PE tests OR
|
197
|
+
# - that windows/aix host are agent only if type is not 'pe'
|
198
|
+
#
|
199
|
+
#@raise [ArgumentError] Raise if argument/options values are invalid
|
200
|
+
def normalize_args
|
201
|
+
|
202
|
+
#use the keyfile if present
|
203
|
+
if @options.has_key?(:keyfile)
|
204
|
+
@options[:ssh][:keys] = [@options[:keyfile]]
|
205
|
+
end
|
206
|
+
|
207
|
+
#split out arguments - these arguments can have the form of arg1,arg2 or [arg] or just arg
|
208
|
+
#will end up being normalized into an array
|
209
|
+
LONG_OPTS.each do |opt|
|
210
|
+
if @options.has_key?(opt)
|
211
|
+
@options[opt] = split_arg(@options[opt])
|
212
|
+
if RB_FILE_OPTS.include?(opt)
|
213
|
+
@options[opt] = file_list(@options[opt])
|
214
|
+
end
|
215
|
+
if opt == :install
|
216
|
+
@options[:install] = parse_git_repos(@options[:install])
|
217
|
+
end
|
218
|
+
else
|
219
|
+
@options[opt] = []
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
#check for valid type
|
224
|
+
if @options[:type] !~ /(pe)|(git)/
|
225
|
+
parser_error "--type must be one of pe or git, not '#{@options[:type]}'"
|
226
|
+
end
|
227
|
+
|
228
|
+
#check for valid fail mode
|
229
|
+
if not ["fast", "stop", nil].include?(@options[:fail_mode])
|
230
|
+
parser_error "--fail-mode must be one of fast, stop"
|
231
|
+
end
|
232
|
+
|
233
|
+
#check for config files necessary for different hypervisors
|
234
|
+
hypervisors = []
|
235
|
+
@options[:HOSTS].each_key do |name|
|
236
|
+
hypervisors << @options[:HOSTS][name][:hypervisor].to_s
|
237
|
+
end
|
238
|
+
hypervisors.uniq!
|
239
|
+
hypervisors.each do |visor|
|
240
|
+
if ['blimpy'].include?(visor)
|
241
|
+
check_yaml_file(@options[:ec2_yaml], "required by #{visor}")
|
242
|
+
end
|
243
|
+
if ['aix', 'solaris', 'vcloud'].include?(visor)
|
244
|
+
check_yaml_file(@options[:dot_fog], "required by #{visor}")
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
#check that roles of hosts make sense
|
249
|
+
# - must be one and only one master
|
250
|
+
roles = []
|
251
|
+
@options[:HOSTS].each_key do |name|
|
252
|
+
roles << @options[:HOSTS][name][:roles]
|
253
|
+
end
|
254
|
+
master = 0
|
255
|
+
roles.each do |role_array|
|
256
|
+
if role_array.include?('master')
|
257
|
+
master += 1
|
258
|
+
end
|
259
|
+
end
|
260
|
+
if master > 1 or master < 1
|
261
|
+
parser_error "One and only one host/node may have the role 'master', fix #{@options[:hosts_file]}"
|
262
|
+
end
|
263
|
+
|
264
|
+
#check that solaris/windows/el-4 boxes are only agents
|
265
|
+
@options[:HOSTS].each_key do |name|
|
266
|
+
host = @options[:HOSTS][name]
|
267
|
+
if (host[:platform] =~ /windows|el-4/) ||
|
268
|
+
(@options.is_pe? && host[:platform] =~ /solaris/)
|
269
|
+
|
270
|
+
test_host_roles(name, host)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
# @api private
|
279
|
+
def test_host_roles(host_name, host_hash)
|
280
|
+
host_roles = host_hash[:roles]
|
281
|
+
if (host_roles - ['agent']).size != 0
|
282
|
+
parser_error "#{host_hash[:platform].to_s} box '#{host_name}' can only have role 'agent', has roles #{host_roles.to_s}"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|