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, :pre_cleanup, :modules]
RB_FILE_OPTS =

These options expand out into an array of .rb files

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeParser

Constructor for Parser



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

def initialize
  @command_line_parser = Beaker::Options::CommandLineParser.new
  @presets             = Beaker::Options::Presets.new
  @validator           = Beaker::Options::Validator.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_hypervisor_config(visor) ⇒ nil

Validate the config file for visor exists.

Parameters:

  • visor (String)

    Hypervisor name

Returns:

  • (nil)

    no return

Raises:

  • (ArgumentError)

    Raises error if config file does not exist or is not valid YAML



333
334
335
336
337
338
339
340
341
# File 'lib/beaker/options/parser.rb', line 333

def check_hypervisor_config(visor)
  if ['blimpy'].include?(visor)
    @validator.check_yaml_file(@options[:ec2_yaml], "required by #{visor}")
  end

  if %w(aix solaris vcloud).include?(visor)
    @validator.check_yaml_file(@options[:dot_fog], "required by #{visor}")
  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



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/beaker/options/parser.rb', line 61

def file_list(paths)
  files = []
  if !paths.empty?
    paths.each do |root|
      @validator.validate_path(root)

      path_files = []
      if File.file?(root)
        path_files << root
      elsif File.directory?(root) #expand and explore
        path_files = Dir.glob(File.join(root, '**/*.rb'))
                         .select { |f| File.file?(f) }
                         .sort_by { |file| [file.count('/'), file] }
      end

      @validator.validate_files(path_files, root)
      files += path_files
    end
  end

  @validator.validate_files(files, paths.to_s)
  files
end

#get_hypervisors(hosts) ⇒ Array

Get a unique list of hypervisors from list of host.

Parameters:

  • hosts (Array)

    beaker hosts

Returns:

  • (Array)

    unique list of hypervisors



322
323
324
325
326
# File 'lib/beaker/options/parser.rb', line 322

def get_hypervisors(hosts)
  hypervisors = []
  hosts.each_key { |name| hypervisors << hosts[name][:hypervisor].to_s }
  hypervisors.uniq
end

#get_roles(hosts) ⇒ Array

Get an array containing lists of roles by parsing each host in hosts.

Parameters:

  • hosts (Array<Array<String>>)

    beaker hosts

Returns:

  • (Array)

    roles [[‘master’, ‘database’], [‘agent’], …]



310
311
312
313
314
315
316
# File 'lib/beaker/options/parser.rb', line 310

def get_roles(hosts)
  roles = []
  hosts.each_key do |name|
    roles << hosts[name][:roles]
  end
  roles
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
- --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
- sets the default host based upon machine definitions
- if an ssh user has been defined make it the host user

Raises:

  • (ArgumentError)

    Raise if argument/options values are invalid



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
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
# File 'lib/beaker/options/parser.rb', line 233

def normalize_args

  @options['HOSTS'].each_key do |name|
    @validator.validate_platform(@options['HOSTS'][name], name)
    @options['HOSTS'][name]['platform'] = Platform.new(@options['HOSTS'][name]['platform'])
  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) && (not @options[opt] == [])
        @options[opt] = file_list(@options[opt])
      end
      if opt == :install
        @options[:install] = parse_git_repos(@options[:install])
      end
    else
      @options[opt] = []
    end
  end

  @validator.validate_fail_mode(@options[:fail_mode])
  @validator.validate_preserve_hosts(@options[:preserve_hosts])

  #check for config files necessary for different hypervisors
  hypervisors = get_hypervisors(@options[:HOSTS])
  hypervisors.each do |visor|
    check_hypervisor_config(visor)
  end

  #check that roles of hosts make sense
  # - must be one and only one master
  master = 0
  roles  = get_roles(@options[:HOSTS])
  roles.each do |role_array|
    master += 1 if role_array.include?('master')
    @validator.validate_frictionless_roles(role_array)
  end

  @validator.validate_master_count(master)

  #check that windows/el-4 boxes are only agents (solaris can be a master in foss cases)
  @options[:HOSTS].each_key do |name|
    host = @options[:HOSTS][name]
    if host[:platform] =~ /windows|el-4/
      test_host_roles(name, host)
    end

    #check to see if a custom user account has been provided, if so use it
    if host[:ssh] && host[:ssh][:user]
      host[:user] = host[:ssh][:user]
    end

    # merge host tags for this host with the global/preset host tags
    host[:host_tags] = @options[:host_tags].merge(host[:host_tags] || {})
  end

  normalize_tags!
  @validator.validate_tags(@options[:tag_includes], @options[:tag_excludes])
  resolve_symlinks!

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

end

#normalize_tags!Object

Normalize include and exclude tags. This modifies @options.



345
346
347
348
349
350
351
352
# File 'lib/beaker/options/parser.rb', line 345

def normalize_tags!
  @options[:tag_includes] ||= ''
  @options[:tag_excludes] ||= ''
  @options[:tag_includes] = @options[:tag_includes].split(',') if @options[:tag_includes].respond_to?(:split)
  @options[:tag_excludes] = @options[:tag_excludes].split(',') if @options[:tag_excludes].respond_to?(:split)
  @options[:tag_includes].map!(&:downcase)
  @options[:tag_excludes].map!(&:downcase)
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.  ARGV or provided arguments array
3.  the 'CONFIG' section of the hosts file
4.  options file values
5.  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)
  @options                        = @presets.presets
  cmd_line_options                = @command_line_parser.parse(args)
  cmd_line_options[:command_line] = ([$0] + args).join(' ')
  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)

  if not @options[:help] and not @options[:beaker_version_print]
    #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) with host file options
    @options      = @options.merge(hosts_options)

    # re-merge the command line options
    #   overwrite options (default, file options, hosts file ) with command line arguments
    @options      = @options.merge(cmd_line_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



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

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

#repoString

Returns the git repository used for git installations

Returns:

  • (String)

    The git repository



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

def repo
  GITREPO
end

#resolve_symlinks!Object

Note:

doing it here allows us to not need duplicate logic, which we would need if we were doing it in the parser (–hosts & –config)

resolves all file symlinks that require it. This modifies @options.

Returns:

  • nil



92
93
94
# File 'lib/beaker/options/parser.rb', line 92

def resolve_symlinks!
  @options[:hosts_file] = File.realpath(@options[:hosts_file]) if @options[:hosts_file]
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



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 130

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

  # default_set? will throw an error if length > 1
  # and return false if no default is set.
  if !@validator.default_set?(default)
    #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(‘,’)



41
42
43
44
45
46
47
48
49
50
51
# File 'lib/beaker/options/parser.rb', line 41

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



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

def usage
  @command_line_parser.usage
end