beaker 0.0.0 → 1.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.
- 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
|