octofacts 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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: []