Class: Beaker::Options::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/beaker/options/parser.rb

Overview

An Object that parses, merges and normalizes all supported Beaker options and arguments

Constant Summary collapse

GITREPO =
'git://github.com/puppetlabs'
LONG_OPTS =

These options can have the form of arg1,arg2 or [arg] or just arg, should default to []

[:helper, :load_path, :tests, :pre_suite, :post_suite, :install, :modules]
RB_FILE_OPTS =

These options expand out into an array of .rb files

[:tests, :pre_suite, :post_suite]
PARSE_ERROR =
Psych::SyntaxError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeParser

Constructor for Parser



162
163
164
165
# File 'lib/beaker/options/parser.rb', line 162

def initialize
   @command_line_parser = Beaker::Options::CommandLineParser.new
   @presets = Beaker::Options::Presets.new
end

Instance Attribute Details

#optionsObject

The OptionsHash of all parsed options



17
18
19
# File 'lib/beaker/options/parser.rb', line 17

def options
  @options
end

Instance Method Details

#check_yaml_file(f, msg = "") ⇒ Object

Determine is a given file exists and is a valid YAML file

Parameters:

  • f (String)

    The YAML file path to examine

  • msg (String) (defaults to: "")

    An options message to report in case of error

Raises:

  • (ArgumentError)

    Raise if file does not exist or is not valid YAML



222
223
224
225
226
227
228
229
230
231
# File 'lib/beaker/options/parser.rb', line 222

def check_yaml_file(f, msg = "")
  if not File.file?(f)
    parser_error "#{f} does not exist (#{msg})"
  end
  begin
    YAML.load_file(f)
  rescue PARSE_ERROR => e
    parser_error "#{f} is not a valid YAML file (#{msg})\n\t#{e}"
  end
end

#file_list(paths) ⇒ Array

Generates a list of files based upon a given path or list of paths.

Looks recursively for .rb files in paths.

Parameters:

  • paths (Array)

    Array of file paths to search for .rb files

Returns:

  • (Array)

    An Array of fully qualified paths to .rb files

Raises:

  • (ArgumentError)

    Raises if no .rb files are found in searched directory or if no .rb files are found overall



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/beaker/options/parser.rb', line 68

def file_list(paths)
  files = []
  if not paths.empty?
    paths.each do |root|
      if File.file?(root)
        files << root
      elsif File.directory?(root) #expand and explore
        discover_files = Dir.glob(
          File.join(root, "**/*.rb")
        ).select { |f| File.file?(f) }
        if discover_files.empty?
          parser_error "empty directory used as an option (#{root})!"
        end
        files += discover_files.sort_by {|file| [file.count("/"), file]}
      else #not a file, not a directory, not nothin'
        parser_error "#{root} used as a file option but is not a file or directory!"
      end
    end
  end
  if files.empty?
    parser_error "no .rb files found in #{paths.to_s}"
  end
  files
end

#normalize_argsObject

Validate all merged options values for correctness

Currently checks:

- each host has a valid platform
- if a keyfile is provided then use it
- paths provided to --test, --pre-suite, --post-suite provided lists of .rb files for testing
- --type is one of 'pe' or 'git'
- --fail-mode is one of 'fast', 'stop' or nil
- if using blimpy hypervisor an EC2 YAML file exists
- if using the aix, solaris, or vcloud hypervisors a .fog file exists
- that one and only one master is defined per set of hosts
- that solaris/windows/aix hosts are agent only for PE tests OR
- that windows/aix host are agent only if type is not 'pe'
- sets the default host based upon machine definitions

Raises:

  • (ArgumentError)

    Raise if argument/options values are invalid



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/beaker/options/parser.rb', line 249

def normalize_args

  @options['HOSTS'].each_key do |name|
    if not @options['HOSTS'][name]['platform']
      parser_error "Host #{name} does not have a platform specified"
    else
      @options['HOSTS'][name]['platform'] = Platform.new(@options['HOSTS'][name]['platform'])
    end
  end

  #use the keyfile if present
  if @options.has_key?(:keyfile)
    @options[:ssh][:keys] = [@options[:keyfile]]
  end

  #split out arguments - these arguments can have the form of arg1,arg2 or [arg] or just arg
  #will end up being normalized into an array
  LONG_OPTS.each do |opt|
    if @options.has_key?(opt)
      @options[opt] = split_arg(@options[opt])
      if RB_FILE_OPTS.include?(opt)
        @options[opt] = file_list(@options[opt])
      end
      if opt == :install
        @options[:install] = parse_git_repos(@options[:install])
      end
    else
      @options[opt] = []
    end
  end

  #check for valid type
  if @options[:type] !~ /pe|git|foss|aio/
    parser_error "--type must be one of pe, git, foss, or aio not '#{@options[:type]}'"
  end

  #check for valid fail mode
  if @options[:fail_mode] !~ /stop|fast|slow/
    parser_error "--fail-mode must be one of fast or slow, not '#{@options[:fail_mode]}'"
  end

  #check for valid preserve_hosts option
  if @options[:preserve_hosts] !~ /always|onfail|onpass|never/
    parser_error "--preserve_hosts must be one of always, onfail, onpass or never, not '#{@options[:preserve_hosts]}'"
  end

  #check for config files necessary for different hypervisors
  hypervisors = []
  @options[:HOSTS].each_key do |name|
    hypervisors << @options[:HOSTS][name][:hypervisor].to_s
  end
  hypervisors.uniq!
  hypervisors.each do |visor|
    if ['blimpy'].include?(visor)
      check_yaml_file(@options[:ec2_yaml], "required by #{visor}")
    end
    if ['aix', 'solaris', 'vcloud'].include?(visor)
      check_yaml_file(@options[:dot_fog], "required by #{visor}")
    end
  end

  #check that roles of hosts make sense
  # - must be one and only one master
  roles = []
  @options[:HOSTS].each_key do |name|
    roles << @options[:HOSTS][name][:roles]
  end
  master = 0
  roles.each do |role_array|
    if role_array.include?('master')
      master += 1
    end
    if role_array.include?('frictionless') and !(role_array & ['master', 'database', 'dashboard', 'console']).empty?
      parser_error "Only agent nodes may have the role 'frictionless', fix #{@options[:hosts_file]}"
    end
  end
  if master > 1
    parser_error "Only one host/node may have the role 'master', fix #{@options[:hosts_file]}"
  end

  #check that solaris/windows/el-4 boxes are only agents
  @options[:HOSTS].each_key do |name|
    host = @options[:HOSTS][name]
    if (host[:platform] =~ /windows|el-4/) ||
       (@options.is_pe? && host[:platform] =~ /solaris/)

      test_host_roles(name, host)
    end
  end

  #set the default role
  set_default_host!(@options[:HOSTS])

end

#parse_args(args = ARGV) ⇒ Object

Parses ARGV or provided arguments array, file options, hosts options and combines with environment variables and preset defaults to generate a Hash representing the Beaker options for a given test run

Order of priority is as follows:

1.  environment variables are given top priority
2.  host file options
3.  the 'CONFIG' section of the hosts file
4.  ARGV or provided arguments array
5.  options file values
6.  default or preset values are given the lowest priority

Parameters:

  • args (Array) (defaults to: ARGV)

    ARGV or a provided arguments array

Raises:

  • (ArgumentError)

    Raises error on bad input



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/beaker/options/parser.rb', line 180

def parse_args(args = ARGV)
  # NOTE on argument precedence:
  # Will use env, then hosts/config file, then command line, then file options


  @options = @presets.presets
  cmd_line_options = @command_line_parser.parse(args)
  file_options = Beaker::Options::OptionsFileParser.parse_options_file(cmd_line_options[:options_file])

  # merge together command line and file_options
  #   overwrite file options with command line options
  cmd_line_and_file_options = file_options.merge(cmd_line_options)

  # merge command line and file options with defaults
  #   overwrite defaults with command line and file options
  @options = @options.merge(cmd_line_and_file_options)
  @options[:command_line] = ([$0] + args).join(' ')

  if not @options[:help] and not @options[:version]
    #read the hosts file that contains the node configuration and hypervisor info
    hosts_options = Beaker::Options::HostsFileParser.parse_hosts_file(@options[:hosts_file])

    # merge in host file vars
    #   overwrite options (default, file options, command line, env) with host file options
    @options = @options.merge(hosts_options)

    # merge in env vars
    #   overwrite options (default, file options, command line, hosts file) with env
    env_vars = @presets.env_vars

    @options = @options.merge(env_vars)

    normalize_args
  end

  @options
end

#parse_git_repos(git_opts) ⇒ Array

Converts array of paths into array of fully qualified git repo URLS with expanded keywords

Supports the following keywords

PUPPET
FACTER
HIERA
HIERA-PUPPET

Examples:

opts = ["PUPPET/3.1"]
parse_git_repos(opts) == ["#{GITREPO}/puppet.git#3.1"]

Parameters:

  • git_opts (Array)

    An array of paths

Returns:

  • (Array)

    An array of fully qualified git repo URLs with expanded keywords



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/beaker/options/parser.rb', line 105

def parse_git_repos(git_opts)
  git_opts.map! { |opt|
    case opt
      when /^PUPPET\//
        opt = "#{GITREPO}/puppet.git##{opt.split('/', 2)[1]}"
      when /^FACTER\//
        opt = "#{GITREPO}/facter.git##{opt.split('/', 2)[1]}"
      when /^HIERA\//
        opt = "#{GITREPO}/hiera.git##{opt.split('/', 2)[1]}"
      when /^HIERA-PUPPET\//
        opt = "#{GITREPO}/hiera-puppet.git##{opt.split('/', 2)[1]}"
    end
    opt
  }
  git_opts
end

#parser_error(msg = "") ⇒ Object

Raises an ArgumentError with associated message

Parameters:

  • msg (String) (defaults to: "")

    The error message to be reported

Raises:

  • (ArgumentError)

    Takes the supplied message and raises it as an ArgumentError



22
23
24
# File 'lib/beaker/options/parser.rb', line 22

def parser_error msg = ""
  raise ArgumentError, msg.to_s
end

#repoString

Returns the git repository used for git installations

Returns:

  • (String)

    The git repository



28
29
30
# File 'lib/beaker/options/parser.rb', line 28

def repo
  GITREPO
end

#set_default_host!(hosts) ⇒ Object

Add the ‘default’ role to the host determined to be the default. If a host already has the role default then do nothing. If more than a single host has the role ‘default’, raise error. Default host determined to be 1) the only host in a single host configuration, 2) the host with the role ‘master’ defined.

Parameters:

  • hosts (Hash)

    A hash of hosts, each identified by a String name. Each named host will have an Array of roles



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/beaker/options/parser.rb', line 127

def set_default_host!(hosts)
  default = []
  master = []
  default_host_name = nil

  #look through the hosts and find any hosts with role 'default' and any hosts with role 'master'
  hosts.each_key do |name|
    host = hosts[name]
    if host[:roles].include?('default')
      default << name
    elsif host[:roles].include?('master')
      master << name
    end
  end

  if not default.empty?
    #we already have a default set, do nothing
    if default.length > 1
      parser_error "Only one host may have the role 'default', default roles assigned to #{default}"
    end
  else
    #no default set, let's make one
    if not master.empty? and master.length == 1
      default_host_name = master[0]
    elsif hosts.length == 1
      default_host_name = hosts.keys[0]
    end
    if default_host_name
      hosts[default_host_name][:roles] << 'default'
    end
  end
end

#split_arg(arg) ⇒ Array

Normalizes argument into an Array. Argument can either be converted into an array of a single value, or can become an array of multiple values by splitting arg over ‘,’. If argument is already an array that array is returned untouched.

Examples:

split_arg([1, 2, 3]) == [1, 2, 3]
split_arg(1) == [1]
split_arg("1,2") == ["1", "2"]
split_arg(nil) == []

Parameters:

  • arg (Array, String)

    Either an array or a string to be split into an array

Returns:

  • (Array)

    An array of the form arg, [arg], or arg.split(‘,’)



48
49
50
51
52
53
54
55
56
57
58
# File 'lib/beaker/options/parser.rb', line 48

def split_arg arg
  arry = []
  if arg.is_a?(Array)
    arry += arg
  elsif arg =~ /,/
    arry += arg.split(',')
  else
    arry << arg
  end
  arry
end

#usageString

Returns a description of Beaker’s supported arguments

Returns:

  • (String)

    The usage String



34
35
36
# File 'lib/beaker/options/parser.rb', line 34

def usage
 @command_line_parser.usage
end