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