octofacts 0.5.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 61e9a30a1976cdb0fb35f94e0d7a9c6447923719
4
+ data.tar.gz: 7db84b41abcd552aedc84346159cd3f67be8d68f
5
+ SHA512:
6
+ metadata.gz: 559197f0fd9d352eb28bd862e3f87f1fb4acf95d25aed162f4f8f7815c87592a99997a5a595829ac95d76f24d2fabddbef8f551f7eff14a75ff453547e00a5a6
7
+ data.tar.gz: 4fc2845a2297be4ebc7225ad0145f186fffb69545f3e98c64d741ade9a807e514d4e881c3556e0d16a612f76c876a878a74ab58d116a79588d0c7864399a0d39
data/.version ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
data/lib/octofacts.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "octofacts/constructors/from_file"
2
+ require "octofacts/constructors/from_index"
3
+ require "octofacts/manipulators"
4
+ require "octofacts/errors"
5
+ require "octofacts/facts"
6
+ require "octofacts/backends/base"
7
+ require "octofacts/backends/index"
8
+ require "octofacts/backends/yaml_file"
9
+ require "octofacts/util/config"
10
+ require "octofacts/util/keys"
11
+ require "octofacts/version"
12
+
13
+ module Octofacts
14
+ #
15
+ end
@@ -0,0 +1,35 @@
1
+ module Octofacts
2
+ module Backends
3
+ # This is a template class to define the minimum API to be implemented
4
+ class Base
5
+ # Returns a hash of the facts selected based on current criteria. Once this is done,
6
+ # it is no longer possible to select, reject, or prefer.
7
+ def facts
8
+ # :nocov:
9
+ raise NotImplementedError, "This method needs to be implemented in the subclass"
10
+ # :nocov:
11
+ end
12
+
13
+ # Filters the possible fact sets based on the criteria.
14
+ def select(*)
15
+ # :nocov:
16
+ raise NotImplementedError, "This method needs to be implemented in the subclass"
17
+ # :nocov:
18
+ end
19
+
20
+ # Removes possible fact sets based on the criteria.
21
+ def reject(*)
22
+ # :nocov:
23
+ raise NotImplementedError, "This method needs to be implemented in the subclass"
24
+ # :nocov:
25
+ end
26
+
27
+ # Reorders possible fact sets based on the criteria.
28
+ def prefer(*)
29
+ # :nocov:
30
+ raise NotImplementedError, "This method needs to be implemented in the subclass"
31
+ # :nocov:
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,127 @@
1
+ require "yaml"
2
+ require "set"
3
+
4
+ module Octofacts
5
+ module Backends
6
+ class Index < Base
7
+ attr_reader :index_path, :fixture_path, :options
8
+ attr_writer :facts
9
+ attr_accessor :nodes
10
+
11
+ def initialize(args = {})
12
+ index_path = Octofacts::Util::Config.fetch(:octofacts_index_path, args)
13
+ fixture_path = Octofacts::Util::Config.fetch(:octofacts_fixture_path, args)
14
+ strict_index = Octofacts::Util::Config.fetch(:octofacts_strict_index, args, false)
15
+
16
+ raise(ArgumentError, "No index passed and ENV['OCTOFACTS_INDEX_PATH'] is not defined") if index_path.nil?
17
+ raise(ArgumentError, "No fixture path passed and ENV['OCTOFACTS_FIXTURE_PATH'] is not defined") if fixture_path.nil?
18
+ raise(Errno::ENOENT, "The index file #{index_path} does not exist") unless File.file?(index_path)
19
+ raise(Errno::ENOENT, "The fixture path #{fixture_path} does not exist") unless File.directory?(fixture_path)
20
+
21
+ @index_path = index_path
22
+ @fixture_path = fixture_path
23
+ @strict_index = strict_index == true || strict_index == "true"
24
+ @facts = nil
25
+ @options = args
26
+
27
+ @node_facts = {}
28
+
29
+ # If there are any other arguments treat them as `select` conditions.
30
+ remaining_args = args.dup
31
+ remaining_args.delete(:octofacts_index_path)
32
+ remaining_args.delete(:octofacts_fixture_path)
33
+ remaining_args.delete(:octofacts_strict_index)
34
+ select(remaining_args) if remaining_args
35
+ end
36
+
37
+ def facts
38
+ @facts ||= node_facts(nodes.first)
39
+ end
40
+
41
+ def select(conditions)
42
+ Octofacts::Util::Keys.desymbolize_keys!(conditions)
43
+ conditions.each do |key, value|
44
+ add_fact_to_index(key) unless indexed_fact?(key)
45
+ matching_nodes = index[key][value.to_s]
46
+ raise Octofacts::Errors::NoFactsError if matching_nodes.nil?
47
+ self.nodes = nodes & matching_nodes
48
+ end
49
+
50
+ self
51
+ end
52
+
53
+ def reject(conditions)
54
+ matching_nodes = nodes
55
+ Octofacts::Util::Keys.desymbolize_keys!(conditions)
56
+ conditions.each do |key, value|
57
+ add_fact_to_index(key) unless indexed_fact?(key)
58
+ unless index[key][value.to_s].nil?
59
+ matching_nodes -= index[key][value.to_s]
60
+ raise Octofacts::Errors::NoFactsError if matching_nodes.empty?
61
+ end
62
+ end
63
+
64
+ self.nodes = matching_nodes
65
+ self
66
+ end
67
+
68
+ def prefer(conditions)
69
+ Octofacts::Util::Keys.desymbolize_keys!(conditions)
70
+ conditions.each do |key, value|
71
+ add_fact_to_index(key) unless indexed_fact?(key)
72
+ matching_nodes = index[key][value.to_s]
73
+ unless matching_nodes.nil?
74
+ self.nodes = (matching_nodes.to_set + nodes.to_set).to_a
75
+ end
76
+ end
77
+
78
+ self
79
+ end
80
+
81
+ private
82
+
83
+ # If a select/reject/prefer is called and the fact is not in the index, this will
84
+ # load the fact files for all currently eligible nodes and then add the fact to the
85
+ # in-memory index. This can be memory-intensive and time-intensive depending on the
86
+ # number of fact fixtures, so it is possible to disable this by passing
87
+ # `:strict_index => true` to the backend constructor, or by setting
88
+ # ENV["OCTOFACTS_STRICT_INDEX"] = "true" in the environment.
89
+ def add_fact_to_index(fact)
90
+ if @strict_index || ENV["OCTOFACTS_STRICT_INDEX"] == "true"
91
+ raise Octofacts::Errors::FactNotIndexed, "Fact #{fact} is not indexed and strict indexing is enabled."
92
+ end
93
+
94
+ index[fact] ||= {}
95
+ nodes.each do |node|
96
+ v = node_facts(node)[fact]
97
+ if v.nil?
98
+ # TODO: Index this somehow
99
+ else
100
+ index[fact][v.to_s] ||= []
101
+ index[fact][v.to_s] << node
102
+ end
103
+ end
104
+ end
105
+
106
+ def nodes
107
+ @nodes ||= index["_nodes"]
108
+ end
109
+
110
+ def index
111
+ @index ||= YAML.safe_load(File.read(index_path))
112
+ end
113
+
114
+ def indexed_fact?(fact)
115
+ index.key?(fact)
116
+ end
117
+
118
+ def node_facts(node)
119
+ @node_facts[node] ||= begin
120
+ f = YAML.safe_load(File.read("#{fixture_path}/#{node}.yaml"))
121
+ Octofacts::Util::Keys.desymbolize_keys!(f)
122
+ f
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,39 @@
1
+ require "yaml"
2
+
3
+ module Octofacts
4
+ module Backends
5
+ class YamlFile < Base
6
+ attr_reader :filename, :options
7
+
8
+ def initialize(filename, options = {})
9
+ raise(Errno::ENOENT, "The file #{filename} does not exist") unless File.file?(filename)
10
+
11
+ @filename = filename
12
+ @options = options
13
+ @facts = nil
14
+ end
15
+
16
+ def facts
17
+ @facts ||= begin
18
+ f = YAML.safe_load(File.read(filename))
19
+ Octofacts::Util::Keys.symbolize_keys!(f)
20
+ f
21
+ end
22
+ end
23
+
24
+ def select(conditions)
25
+ Octofacts::Util::Keys.symbolize_keys!(conditions)
26
+ raise Octofacts::Errors::NoFactsError unless (conditions.to_a - facts.to_a).empty?
27
+ end
28
+
29
+ def reject(conditions)
30
+ Octofacts::Util::Keys.symbolize_keys!(conditions)
31
+ raise Octofacts::Errors::NoFactsError if (conditions.to_a - facts.to_a).empty?
32
+ end
33
+
34
+ def prefer(conditions)
35
+ # noop
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ module Octofacts
2
+ # Octofacts.from_file(filename, options) - Construct Octofacts::Facts from a filename.
3
+ #
4
+ # filename - Relative or absolute path to the file containing the facts.
5
+ # opts[:octofacts_fixture_path] - Directory where fact fixture files are found (default: ENV["OCTOFACTS_FIXTURE_PATH"])
6
+ #
7
+ # Returns an Octofacts::Facts object.
8
+ def self.from_file(filename, opts = {})
9
+ unless filename.start_with? "/"
10
+ dir = Octofacts::Util::Config.fetch(:octofacts_fixture_path, opts)
11
+ raise ArgumentError, ".from_file needs to know :octofacts_fixture_path or environment OCTOFACTS_FIXTURE_PATH" unless dir
12
+ raise Errno::ENOENT, "The provided fixture path #{dir} is invalid" unless File.directory?(dir)
13
+ filename = File.join(dir, filename)
14
+ end
15
+
16
+ Octofacts::Facts.new(backend: Octofacts::Backends::YamlFile.new(filename), options: opts)
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ module Octofacts
2
+ # Octofacts.from_index(options) - Construct Octofacts::Facts from an index file.
3
+ #
4
+ # Returns an Octofacts::Facts object.
5
+ def self.from_index(opts = {})
6
+ Octofacts::Facts.new(backend: Octofacts::Backends::Index.new(opts), options: opts)
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Octofacts
2
+ class Errors
3
+ class FactNotIndexed < RuntimeError; end
4
+ class OperationNotPermitted < RuntimeError; end
5
+ class NoFactsError < RuntimeError; end
6
+ end
7
+ end
@@ -0,0 +1,121 @@
1
+ require "yaml"
2
+
3
+ module Octofacts
4
+ class Facts
5
+ attr_writer :facts
6
+
7
+ # Constructor.
8
+ #
9
+ # backend - An Octofacts::Backends object (preferred)
10
+ # options - Additional options (e.g., downcase keys, symbolize keys, etc.)
11
+ def initialize(args = {})
12
+ @backend = args.fetch(:backend)
13
+ @facts_manipulated = false
14
+
15
+ options = args.fetch(:options, {})
16
+ @downcase_keys = args.fetch(:downcase_keys, options.fetch(:downcase_keys, true))
17
+ end
18
+
19
+ # To hash. (This method is intended to be called by rspec-puppet.)
20
+ #
21
+ # This loads the fact file and downcases, desymbolizes, and otherwise manipulates the keys.
22
+ # The output is suitable for consumption by rspec-puppet.
23
+ def to_hash
24
+ f = facts
25
+ downcase_keys!(f) if @downcase_keys
26
+ desymbolize_keys!(f)
27
+ f
28
+ end
29
+ alias_method :to_h, :to_hash
30
+
31
+ # To fact hash. (This method is intended to be called by developers.)
32
+ #
33
+ # This loads the fact file and downcases, symbolizes, and otherwise manipulates the keys.
34
+ # This is very similar to 'to_hash' except that it returns symbolized keys.
35
+ # The output is suitable for consumption by rspec-puppet (note that rspec-puppet will
36
+ # de-symbolize all the keys in the hash object though).
37
+ def facts
38
+ @facts ||= begin
39
+ f = @backend.facts
40
+ downcase_keys!(f) if @downcase_keys
41
+ symbolize_keys!(f)
42
+ f
43
+ end
44
+ end
45
+
46
+ # Calls to backend methods.
47
+ #
48
+ # These calls are passed through directly to backend methods.
49
+ def select(*args)
50
+ if @facts_manipulated
51
+ raise Octofacts::Errors::OperationNotPermitted, "Cannot call select() after backend facts have been manipulated"
52
+ end
53
+ @backend.select(*args)
54
+ self
55
+ end
56
+
57
+ def reject(*args)
58
+ if @facts_manipulated
59
+ raise Octofacts::Errors::OperationNotPermitted, "Cannot call reject() after backend facts have been manipulated"
60
+ end
61
+ @backend.reject(*args)
62
+ self
63
+ end
64
+
65
+ def prefer(*args)
66
+ if @facts_manipulated
67
+ raise Octofacts::Errors::OperationNotPermitted, "Cannot call prefer() after backend facts have been manipulated"
68
+ end
69
+ @backend.prefer(*args)
70
+ self
71
+ end
72
+
73
+ # Missing method - this is used to dispatch to manipulators or to call a Hash method in the facts.
74
+ #
75
+ # Try calling a Manipulator method, delegate to the facts hash or else error out.
76
+ #
77
+ # Returns this object (so that calls to manipulators can be chained).
78
+ def method_missing(name, *args, &block)
79
+ if Octofacts::Manipulators.run(self, name, *args, &block)
80
+ @facts_manipulated = true
81
+ return self
82
+ end
83
+
84
+ if facts.respond_to?(name, false)
85
+ if args[0].is_a?(String) || args[0].is_a?(Symbol)
86
+ args[0] = string_or_symbolized_key(args[0])
87
+ end
88
+ return facts.send(name, *args)
89
+ end
90
+
91
+ raise NameError, "Unknown method '#{name}' in #{self.class}"
92
+ end
93
+
94
+ def respond_to?(method, include_all = false)
95
+ camelized_name = (method.to_s).split("_").collect(&:capitalize).join
96
+ super || Kernel.const_get("Octofacts::Manipulators::#{camelized_name}")
97
+ rescue NameError
98
+ return facts.respond_to?(method, include_all)
99
+ end
100
+
101
+ private
102
+
103
+ def downcase_keys!(input)
104
+ Octofacts::Util::Keys.downcase_keys!(input)
105
+ end
106
+
107
+ def symbolize_keys!(input)
108
+ Octofacts::Util::Keys.symbolize_keys!(input)
109
+ end
110
+
111
+ def desymbolize_keys!(input)
112
+ Octofacts::Util::Keys.desymbolize_keys!(input)
113
+ end
114
+
115
+ def string_or_symbolized_key(input)
116
+ return input.to_s if facts.key?(input.to_s)
117
+ return input.to_sym if facts.key?(input.to_sym)
118
+ input
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,27 @@
1
+ require_relative "manipulators/replace"
2
+
3
+ # Octofacts::Manipulators - our fact manipulation API.
4
+ # Each method in Octofacts::Manipulators will operate on one fact set at a time. These
5
+ # methods do not need to be aware of the existence of multiple fact sets.
6
+ module Octofacts
7
+ class Manipulators
8
+ # Locate and run manipulator.
9
+ #
10
+ # Returns true if the manipulator was located and executed, false otherwise.
11
+ def self.run(obj, name, *args, &block)
12
+ camelized_name = (name.to_s).split("_").collect(&:capitalize).join
13
+
14
+ begin
15
+ manipulator = Kernel.const_get("Octofacts::Manipulators::#{camelized_name}")
16
+ rescue NameError
17
+ return false
18
+ end
19
+
20
+ raise "Unable to run manipulator method '#{name}' on object type #{obj.class}" unless obj.is_a?(Octofacts::Facts)
21
+ facts = obj.facts
22
+ manipulator.send(:execute, facts, *args, &block)
23
+ obj.facts = facts
24
+ true
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,110 @@
1
+ module Octofacts
2
+ class Manipulators
3
+ # Delete a fact from a hash.
4
+ #
5
+ # fact_set - The hash of facts
6
+ # fact_name - Fact to delete, either as a string, symbol, or "multi::level::hash::key"
7
+ def self.delete(fact_set, fact_name)
8
+ if fact_name.to_s !~ /::/
9
+ fact_set.delete(fact_name.to_sym)
10
+ return
11
+ end
12
+
13
+ # Convert level1::level2::level3 into { "level1" => { "level2" => { "level3" => ... } } }
14
+ # The delimiter is 2 colons.
15
+ levels = fact_name.to_s.split("::")
16
+ key_name = levels.pop.to_sym
17
+ pointer = fact_set
18
+ while levels.any?
19
+ next_key = levels.shift.to_sym
20
+ return unless pointer.key?(next_key) && pointer[next_key].is_a?(Hash)
21
+ pointer = pointer[next_key]
22
+ end
23
+
24
+ pointer.delete(key_name)
25
+ end
26
+
27
+ # Determine if a fact exists in a hash.
28
+ #
29
+ # fact_set - The hash of facts
30
+ # fact_name - Fact to check, either as a string, symbol, or "multi::level::hash::key"
31
+ #
32
+ # Returns true if the fact exists, false otherwise.
33
+ def self.exists?(fact_set, fact_name)
34
+ !get(fact_set, fact_name).nil?
35
+ end
36
+
37
+ # Retrieves the value of a fact from a hash.
38
+ #
39
+ # fact_set - The hash of facts
40
+ # fact_name - Fact to retrieve, either as a string, symbol, or "multi::level::hash::key"
41
+ #
42
+ # Returns the value of the fact.
43
+ def self.get(fact_set, fact_name)
44
+ return fact_set[fact_name.to_sym] unless fact_name.to_s =~ /::/
45
+
46
+ # Convert level1::level2::level3 into { "level1" => { "level2" => { "level3" => ... } } }
47
+ # The delimiter is 2 colons.
48
+ levels = fact_name.to_s.split("::")
49
+ key_name = levels.pop.to_sym
50
+ pointer = fact_set
51
+ while levels.any?
52
+ next_key = levels.shift.to_sym
53
+ return unless pointer.key?(next_key) && pointer[next_key].is_a?(Hash)
54
+ pointer = pointer[next_key]
55
+ end
56
+ pointer[key_name]
57
+ end
58
+
59
+ # Sets the value of a fact in a hash.
60
+ #
61
+ # The new value can be a string, integer, etc., which will directly set the value of
62
+ # the fact. Instead, you may pass a lambda in place of the value, which will evaluate
63
+ # with three parameters: lambda { |fact_set|, |fact_name|, |old_value| ... },
64
+ # or with one parameter: lambda { |old_value| ...}.
65
+ # If the value of the fact as evaluated is `nil` then the fact is deleted instead of set.
66
+ #
67
+ # fact_set - The hash of facts
68
+ # fact_name - Fact to set, either as a string, symbol, or "multi::level::hash::key"
69
+ # value - A lambda with new code, or a string, integer, etc.
70
+ def self.set(fact_set, fact_name, value)
71
+ fact = fact_name.to_s
72
+
73
+ if fact !~ /::/
74
+ fact_set[fact_name.to_sym] = _set(fact_set, fact_name, fact_set[fact_name.to_sym], value)
75
+ fact_set.delete(fact_name.to_sym) if fact_set[fact_name.to_sym].nil?
76
+ return
77
+ end
78
+
79
+ # Convert level1::level2::level3 into { "level1" => { "level2" => { "level3" => ... } } }
80
+ # The delimiter is 2 colons.
81
+ levels = fact_name.to_s.split("::")
82
+ key_name = levels.pop.to_sym
83
+ pointer = fact_set
84
+ while levels.any?
85
+ next_key = levels.shift.to_sym
86
+ pointer[next_key] = {} unless pointer[next_key].is_a? Hash
87
+ pointer = pointer[next_key]
88
+ end
89
+ pointer[key_name] = _set(fact_set, fact_name, pointer[key_name], value)
90
+ pointer.delete(key_name) if pointer[key_name].nil?
91
+ end
92
+
93
+ # Internal method: Determine the value you're setting to.
94
+ #
95
+ # This handles dispatching to the lambda function or putting the new value in place.
96
+ def self._set(fact_set, fact_name, old_value, new_value)
97
+ if new_value.is_a?(Proc)
98
+ if new_value.arity == 1
99
+ new_value.call(old_value)
100
+ elsif new_value.arity == 3
101
+ new_value.call(fact_set, fact_name, old_value)
102
+ else
103
+ raise ArgumentError, "Lambda method expected 1 or 3 parameters, got #{new_value.arity}"
104
+ end
105
+ else
106
+ new_value
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "base"
2
+
3
+ module Octofacts
4
+ class Manipulators
5
+ class Replace < Octofacts::Manipulators
6
+ # Public: Executor for the .replace command.
7
+ #
8
+ # Sets the fact to the specified value. If the fact didn't exist before, it's created.
9
+ #
10
+ # facts - Hash of current facts
11
+ # args - Arguments, here consisting of an array of hashes with replacement parameters
12
+ def self.execute(facts, *args, &_block)
13
+ args.each do |arg|
14
+ raise ArgumentError, "Must pass a hash of target facts to .replace - got #{arg}" unless arg.is_a?(Hash)
15
+ arg.each { |key, val| set(facts, key, val) }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # Retrieves configuration parameters from:
2
+ # - input hash
3
+ # - rspec configuration
4
+ # - environment
5
+ module Octofacts
6
+ module Util
7
+ class Config
8
+ # Fetch a variable from various sources
9
+ def self.fetch(variable_name, hash_in = {}, default = nil)
10
+ if hash_in.key?(variable_name)
11
+ return hash_in[variable_name]
12
+ end
13
+
14
+ begin
15
+ rspec_value = RSpec.configuration.send(variable_name)
16
+ return rspec_value if rspec_value
17
+ rescue NoMethodError
18
+ # Just skip if undefined
19
+ end
20
+
21
+ env_key = variable_name.to_s.upcase
22
+ return ENV[env_key] if ENV.key?(env_key)
23
+
24
+ default
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,51 @@
1
+ module Octofacts
2
+ module Util
3
+ class Keys
4
+ # Downcase all keys.
5
+ #
6
+ # rspec-puppet does this internally, but depending on how Octofacts is called, this logic may not
7
+ # be triggered. Therefore, we downcase all keys ourselves.
8
+ def self.downcase_keys!(input)
9
+ raise ArgumentError, "downcase_keys! expects Hash, not #{input.class}" unless input.is_a?(Hash)
10
+
11
+ input_keys = input.keys.dup
12
+ input_keys.each do |k|
13
+ downcase_keys!(input[k]) if input[k].is_a?(Hash)
14
+ next if k.to_s == k.to_s.downcase
15
+ new_key = k.is_a?(Symbol) ? k.to_s.downcase.to_sym : k.downcase
16
+ input[new_key] = input.delete(k)
17
+ end
18
+ input
19
+ end
20
+
21
+ # Symbolize all keys.
22
+ #
23
+ # Many people work with symbolized keys rather than string keys when dealing with fact fixtures.
24
+ # This method recursively converts all keys to symbols.
25
+ def self.symbolize_keys!(input)
26
+ raise ArgumentError, "symbolize_keys! expects Hash, not #{input.class}" unless input.is_a?(Hash)
27
+
28
+ input_keys = input.keys.dup
29
+ input_keys.each do |k|
30
+ symbolize_keys!(input[k]) if input[k].is_a?(Hash)
31
+ input[k.to_sym] = input.delete(k) unless k.is_a?(Symbol)
32
+ end
33
+ input
34
+ end
35
+
36
+ # De-symbolize all keys.
37
+ #
38
+ # rspec-puppet ultimately wants stringified keys, so this is a method to turn symbols back into strings.
39
+ def self.desymbolize_keys!(input)
40
+ raise ArgumentError, "desymbolize_keys! expects Hash, not #{input.class}" unless input.is_a?(Hash)
41
+
42
+ input_keys = input.keys.dup
43
+ input_keys.each do |k|
44
+ desymbolize_keys!(input[k]) if input[k].is_a?(Hash)
45
+ input[k.to_s] = input.delete(k) unless k.is_a?(String)
46
+ end
47
+ input
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module Octofacts
2
+ VERSION = File.read(File.expand_path("../../.version", File.dirname(__FILE__))).strip
3
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: octofacts
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - GitHub, Inc.
8
+ - Kevin Paulisse
9
+ - Antonio Santos
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2017-10-06 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: |
16
+ Octofacts provides fact fixtures built from recently-updated Puppet facts to rspec-puppet tests.
17
+ email: opensource+octofacts@github.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".version"
23
+ - lib/octofacts.rb
24
+ - lib/octofacts/backends/base.rb
25
+ - lib/octofacts/backends/index.rb
26
+ - lib/octofacts/backends/yaml_file.rb
27
+ - lib/octofacts/constructors/from_file.rb
28
+ - lib/octofacts/constructors/from_index.rb
29
+ - lib/octofacts/errors.rb
30
+ - lib/octofacts/facts.rb
31
+ - lib/octofacts/manipulators.rb
32
+ - lib/octofacts/manipulators/base.rb
33
+ - lib/octofacts/manipulators/replace.rb
34
+ - lib/octofacts/util/config.rb
35
+ - lib/octofacts/util/keys.rb
36
+ - lib/octofacts/version.rb
37
+ homepage: https://github.com/github/octofacts
38
+ licenses:
39
+ - MIT
40
+ metadata: {}
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 2.1.0
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubyforge_project:
57
+ rubygems_version: 2.2.5
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: Run your rspec-puppet tests against fake hosts that present almost real facts
61
+ test_files: []