File: facterdb.rb

package info (click to toggle)
facterdb 1.12.1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 16,624 kB
  • sloc: ruby: 544; sh: 356; xml: 195; makefile: 9
file content (145 lines) | stat: -rw-r--r-- 5,634 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
require 'facter'
require 'jgrep'

module FacterDB
  module Errors
    class InvalidFilter < RuntimeError; end
  end

  # @return [String] -  returns a giant incomprehensible string of concatenated json data
  def self.database
    @database ||= "[#{facterdb_fact_files.map { |f| read_json_file(f) }.join(',')}]\n"
  end

  # @note Call this method at the end of test suite, for example via after(:suite), to reclaim back the memory required to hold json data and filter cache
  def self.cleanup
    @database = nil
    Thread.current[:facterdb_last_filter_seen] = nil
    Thread.current[:facterdb_last_facts_seen] = nil
  end

  # @return [Boolean] - returns true if we should use the default facterdb database, false otherwise
  # @note If the user passes anything to the FACTERDB_SKIP_DEFAULTDB environment variable we assume
  # they want to skip the default db
  def self.use_defaultdb?
    ENV['FACTERDB_SKIP_DEFAULTDB'].nil?
  end

  # @return [Boolean] - returns true if we should inject the source file name and file path into the json factsets.
  # The default is false.
  def self.inject_source?
    !ENV['FACTERDB_INJECT_SOURCE'].nil?
  end

  def self.read_json_file(f)
    content = File.read(f)
    return content unless inject_source?
    # Find the opening brace
    first_brace = content.index('{')
    return content if first_brace.nil?
    # Inject source file information
    json_injection =  "\"_facterdb_filename\": #{File.basename(f).to_json}, "
    json_injection += "\"_facterdb_path\": #{File.expand_path(f).to_json}, "
    content.insert(first_brace + 1, json_injection)
  end
  private_class_method :read_json_file

  # @return [Array[String]] -  list of all files found in the default facterdb facts path
  def self.default_fact_files
    return [] unless use_defaultdb?
    proj_root = File.join(File.dirname(File.dirname(__FILE__)))
    facts_dir = File.expand_path(File.join(proj_root, 'facts'))
    Dir.glob(File.join(facts_dir, "**", '*.facts'))
  end

  # @return [Array[String]] -  list of all files found in the user supplied facterdb facts path
  # @param fact_paths [String] - a comma separated list of paths to search for fact files
  def self.external_fact_files(fact_paths = ENV['FACTERDB_SEARCH_PATHS'])
    fact_paths ||= ''
    return [] if fact_paths.empty?
    paths = fact_paths.split(File::PATH_SEPARATOR).map do |fact_path|
      unless File.directory?(fact_path)
        warn("[FACTERDB] Ignoring external facts path #{fact_path} as it is not a directory")
        next nil
      end
      fact_path = fact_path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
      File.join(fact_path.strip, '**', '*.facts')
    end.compact
    Dir.glob(paths)
  end

  # @return [Array[String]] -  list of all files found in the default facterdb facts path and user supplied path
  # @note external fact files supplied by the user will take precedence over default fact files found in this gem
  def self.facterdb_fact_files
    (external_fact_files + default_fact_files).uniq
  end

  def self.get_os_facts(facter_version='*', filter=[])
    if facter_version == '*'
      if filter.is_a?(Array)
        filter_str = filter.map { |f| f.map { |k,v | "#{k}=#{v}" }.join(' and ') }.join(' or ')
      elsif filter.is_a?(Hash)
        filter_str = filter.map { |k,v | "#{k}=#{v}" }.join(' and ')
      elsif filter.is_a?(String)
        filter_str = filter
      else
        raise 'filter must be either an Array a Hash or a String'
      end
    else
      if filter.is_a?(Array)
        filter_str = "facterversion=/^#{facter_version}/ and (#{filter.map { |f| f.map { |k,v | "#{k}=#{v}" }.join(' and ') }.join(' or ')})"
      elsif filter.is_a?(Hash)
        filter_str = "facterversion=/^#{facter_version}/ and (#{filter.map { |k,v | "#{k}=#{v}" }.join(' and ')})"
      elsif filter.is_a?(String)
        filter_str = "facterversion=/^#{facter_version}/ and (#{filter})"
      else
        raise 'filter must be either an Array a Hash or a String'
      end
    end

    warn "[DEPRECATION] `get_os_facts` is deprecated. Please use `get_facts(#{filter_str})` instead."

    get_facts(filter_str)
  end

  # @return [String] - the string filter
  # @param filter [Object] - The filter to convert to jgrep string
  def self.generate_filter_str(filter=nil)
    case filter
    when ::Array
      '(' + filter.map { |f| f.map { |k,v | "#{k}=#{v}" }.join(' and ') }.join(') or (') + ')'
    when ::Hash
      filter.map { |k,v | "#{k}=#{v}" }.join(' and ')
    when ::String
      filter
    when ::NilClass
      ''
    else
      raise Errors::InvalidFilter, "filter must be either an Array a Hash, String, or nil, received #{filter.class}"
    end
  end

  # @return [Boolean] - true if the filter is valid against the jgrep filter validator
  # @param filter [Object] - The filter to convert to jgrep string
  def self.valid_filters?(filters)
    filter_str = generate_filter_str(filters)
    JGrep.validate_filters(filter_str).nil?
  rescue RuntimeError
    false
  end

  # @return [Array] - array of hashes of facts
  # @param filter [Object] - The filter to convert to jgrep string
  def self.get_facts(filter=nil, cache=true)
    if cache && filter && filter == Thread.current[:facterdb_last_filter_seen]
      return Thread.current[:facterdb_last_facts_seen]
    end
    filter_str = generate_filter_str(filter)
    result = JGrep.jgrep(database, filter_str).map { |hash| Hash[hash.map{ |k, v| [k.to_sym, v] }] }
    if cache
      Thread.current[:facterdb_last_filter_seen] = filter
      Thread.current[:facterdb_last_facts_seen] = result
    end
    result
  end
end