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 +7 -0
- data/.version +1 -0
- data/lib/octofacts.rb +15 -0
- data/lib/octofacts/backends/base.rb +35 -0
- data/lib/octofacts/backends/index.rb +127 -0
- data/lib/octofacts/backends/yaml_file.rb +39 -0
- data/lib/octofacts/constructors/from_file.rb +18 -0
- data/lib/octofacts/constructors/from_index.rb +8 -0
- data/lib/octofacts/errors.rb +7 -0
- data/lib/octofacts/facts.rb +121 -0
- data/lib/octofacts/manipulators.rb +27 -0
- data/lib/octofacts/manipulators/base.rb +110 -0
- data/lib/octofacts/manipulators/replace.rb +20 -0
- data/lib/octofacts/util/config.rb +28 -0
- data/lib/octofacts/util/keys.rb +51 -0
- data/lib/octofacts/version.rb +3 -0
- metadata +61 -0
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,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
|
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: []
|