Class: Cisco::CommandReference

Inherits:
Object
  • Object
show all
Defined in:
lib/cisco_node_utils/command_reference.rb

Overview

Builds reference hash for the platform specified in the product id.

Constant Summary collapse

KNOWN_FILTERS =
%w(cli_nexus)
@@debug =

rubocop:disable Style/ClassVars

false

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(product: nil, platform: nil, cli: false, files: nil) ⇒ CommandReference

Constructor. Normal usage is to pass product, platform, cli, in which case usual YAML files will be located then the list will be filtered down to only those matching the given settings. For testing purposes (only!) you can pass an explicit list of files to load instead. This list will NOT be filtered further by product_id.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/cisco_node_utils/command_reference.rb', line 268

def initialize(product:  nil,
               platform: nil,
               cli:      false,
               files:    nil)
  @product_id = product
  @platform = platform
  @cli = cli
  @hash = {}
  if files
    @files = files
  else
    @files = Dir.glob(__dir__ + '/cmd_ref/*.yaml')
  end

  build_cmd_ref
end

Instance Attribute Details

#cliObject (readonly)

Returns the value of attribute cli.



260
261
262
# File 'lib/cisco_node_utils/command_reference.rb', line 260

def cli
  @cli
end

#filesObject (readonly)

Returns the value of attribute files.



260
261
262
# File 'lib/cisco_node_utils/command_reference.rb', line 260

def files
  @files
end

#platformObject (readonly)

Returns the value of attribute platform.



260
261
262
# File 'lib/cisco_node_utils/command_reference.rb', line 260

def platform
  @platform
end

#product_idObject (readonly)

Returns the value of attribute product_id.



260
261
262
# File 'lib/cisco_node_utils/command_reference.rb', line 260

def product_id
  @product_id
end

Class Method Details

.debug=(value) ⇒ Object



254
255
256
257
258
# File 'lib/cisco_node_utils/command_reference.rb', line 254

def self.debug=(value)
  fail ArgumentError, 'Debug must be boolean' unless value == true ||
                                                     value == false
  @@debug = value # rubocop:disable Style/ClassVars
end

.filter_hash(hash, platform: nil, product_id: nil, cli: false, allow_unknown_keys: true) ⇒ Object

Helper method Given a Hash of command reference data as read from YAML, does:

  • Filter out any API-specific data not applicable to this API

  • Filter any platform-specific data not applicable to this product_id

Returns the filtered hash (possibly empty)



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/cisco_node_utils/command_reference.rb', line 361

def self.filter_hash(hash,
                     platform:           nil,
                     product_id:         nil,
                     cli:                false,
                     allow_unknown_keys: true)
  result = {}

  exclude = hash.delete('_exclude') || []
  exclude.each do |value|
    if key_match(value, platform, product_id, cli) == true
      debug 'Exclude this product (#{product_id}, #{value})'
      return result
    end
  end

  # to_inspect: sub-keys we want to recurse into
  to_inspect = []
  # regexp_match: did we find a product_id regexp that matches?
  regexp_match = false

  hash.each do |key, value|
    if CmdRef.keys.include?(key)
      result[key] = value
    elsif key != 'else'
      match = key_match(key, platform, product_id, cli)
      next if match == false
      if match == :unknown
        fail "Unrecognized key '#{key}'" unless allow_unknown_keys
      end
      regexp_match = true if match == true
      to_inspect << key
    end
  end
  # If we didn't find any platform regexp match,
  # and an 'else' sub-hash is provided, descend into 'else'
  to_inspect << 'else' if hash.key?('else') && !regexp_match
  # Recurse! Sub-hashes can override the base hash
  to_inspect.each do |key|
    unless hash[key].is_a?(Hash)
      result[key] = hash[key]
      next
    end
    begin
      result[key] = filter_hash(hash[key],
                                platform:           platform,
                                product_id:         product_id,
                                cli:                cli,
                                allow_unknown_keys: false)
    rescue RuntimeError => e
      raise "[#{key}]: #{e}"
    end
  end
  result
end

.hash_merge(input_hash, base_hash = nil) ⇒ Object

Helper method Given a suitably filtered Hash of command reference data, does:

  • Inherit data from the given base_hash (if any) and extend/override it with the given input data.

  • Append ‘config_set_append’ data to any existing ‘config_set’ data

  • Append ‘config_get_token_append’ data to ‘config_get_token’, ditto



429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/cisco_node_utils/command_reference.rb', line 429

def self.hash_merge(input_hash, base_hash=nil)
  result = base_hash
  result ||= {}
  # to_inspect: sub-hashes we want to recurse into
  to_inspect = []

  input_hash.each do |key, value|
    if CmdRef.keys.include?(key)
      if key == 'config_set_append'
        result['config_set'] = value_append(result['config_set'], value)
      elsif key == 'config_get_token_append'
        result['config_get_token'] = value_append(
          result['config_get_token'], value)
      else
        result[key] = value
      end
    elsif value.is_a?(Hash)
      to_inspect << value
    else
      fail "Unexpected non-hash data: #{value}"
    end
  end
  # Recurse! Sub-hashes can override the base hash
  to_inspect.each do |hash|
    result = hash_merge(hash, result)
  end
  result
end

.key_match(key, platform, product_id, cli) ⇒ Object



344
345
346
347
348
349
350
351
352
353
354
# File 'lib/cisco_node_utils/command_reference.rb', line 344

def self.key_match(key, platform, product_id, cli)
  if key[0] == '/' && key[-1] == '/'
    # It's a product-id regexp. Does it match our given product_id?
    return Regexp.new(key[1..-2]) =~ product_id ? true : false
  elsif KNOWN_FILTERS.include?(key)
    return false if key.match(/cli/) && !cli
    return Regexp.new(platform.to_s) =~ key ? true : false
  else
    return :unknown
  end
end

.value_append(base_value, new_value) ⇒ Object

Helper method. Combines the two given values (either or both of which may be arrays) into a single combined array value_append(‘foo’, ‘bar’) ==> [‘foo’, ‘bar’] value_append(‘foo’, [‘bar’, ‘baz’]) ==> [‘foo’, ‘bar’, ‘baz’]



463
464
465
466
467
# File 'lib/cisco_node_utils/command_reference.rb', line 463

def self.value_append(base_value, new_value)
  base_value = [base_value] unless base_value.is_a?(Array)
  new_value = [new_value] unless new_value.is_a?(Array)
  base_value + new_value
end

Instance Method Details

#build_cmd_refObject

Build complete reference hash.



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
# File 'lib/cisco_node_utils/command_reference.rb', line 286

def build_cmd_ref
  # Example id's: N3K-C3048TP-1GE, N3K-C3064PQ-10GE, N7K-C7009, N7K-C7009

  debug "Product: #{@product_id}"
  debug "Files being used: #{@files.join(', ')}"

  @files.each do |file|
    feature = File.basename(file).split('.')[0]
    debug "Processing file '#{file}' as feature '#{feature}'"
    feature_hash = load_yaml(file)
    if feature_hash.empty?
      debug "Feature #{feature} is empty"
      next
    end
    feature_hash = filter_hash(feature_hash)
    if feature_hash.empty?
      debug "Feature #{feature} is excluded"
      @hash[feature] = UnsupportedCmdRef.new(feature, nil, file)
      next
    end

    base_hash = {}
    if feature_hash.key?('_template')
      base_hash = CommandReference.hash_merge(feature_hash['_template'])
    end

    feature_hash.each do |name, value|
      fail "No entries under '#{name}' in '#{file}'" if value.nil?
      @hash[feature] ||= {}
      if value.empty?
        @hash[feature][name] = UnsupportedCmdRef.new(feature, name, file)
      else
        values = CommandReference.hash_merge(value, base_hash.clone)
        @hash[feature][name] = CmdRef.new(feature, name, values, file)
      end
    end
  end
end

#debug(text) ⇒ Object

Print debug statements



338
339
340
# File 'lib/cisco_node_utils/command_reference.rb', line 338

def debug(text)
  puts "DEBUG: #{text}" if @@debug
end

#empty?Boolean

Returns:

  • (Boolean)


333
334
335
# File 'lib/cisco_node_utils/command_reference.rb', line 333

def empty?
  @hash.empty?
end

#filter_hash(input_hash) ⇒ Object



416
417
418
419
420
421
# File 'lib/cisco_node_utils/command_reference.rb', line 416

def filter_hash(input_hash)
  CommandReference.filter_hash(input_hash,
                               platform:   platform,
                               product_id: product_id,
                               cli:        cli)
end

#load_yaml(yaml_file) ⇒ Object

Read in yaml file. The expectation is that a file corresponds to a feature



561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
# File 'lib/cisco_node_utils/command_reference.rb', line 561

def load_yaml(yaml_file)
  fail "File #{yaml_file} doesn't exist." unless File.exist?(yaml_file)
  # Parse YAML file into a tree of nodes
  # Psych::SyntaxError doesn't inherit from StandardError in some versions,
  # so we want to explicitly catch it if using Psych.
  rescue_errors = [::StandardError, ::Psych::SyntaxError]
  yaml_parsed = File.open(yaml_file, 'r') do |f|
    begin
      YAML.parse(f)
    rescue *rescue_errors => e
      raise "unable to parse #{yaml_file}: #{e}"
    end
  end
  return {} unless yaml_parsed
  # Validate the node tree
  validate_yaml(yaml_parsed, yaml_file)
  # If validation passed, convert the node tree to a Ruby Hash.
  yaml_parsed.transform
end

#lookup(feature, name) ⇒ Object

Get the command reference



326
327
328
329
330
331
# File 'lib/cisco_node_utils/command_reference.rb', line 326

def lookup(feature, name)
  value = @hash[feature]
  value = value[name] if value.is_a? Hash
  fail IndexError, "No CmdRef defined for #{feature}, #{name}" if value.nil?
  value
end

#to_sObject



581
582
583
# File 'lib/cisco_node_utils/command_reference.rb', line 581

def to_s
  @hash.each_value { |names| names.each_value(&:to_s) }
end