rfacter 0.0.1

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.
@@ -0,0 +1,42 @@
1
+ require 'logger'
2
+
3
+ require 'rfacter'
4
+
5
+ # RFacter Logger class
6
+ #
7
+ # This class provides all the methods of a standard Ruby Logger plus the
8
+ # following methods used by the Facter API:
9
+ #
10
+ # - `warnonce`
11
+ # - `debugonce`
12
+ # - `log_exception`
13
+ #
14
+ # @since 0.1.0
15
+ class RFacter::Util::Logger < ::Logger
16
+ @@warn_messages = Hash.new
17
+ @@debug_messages = Hash.new
18
+
19
+ def warnonce(msg)
20
+ if @@warn_messages[msg].nil?
21
+ self.warn(msg)
22
+ @@warn_messages[msg] = true
23
+ end
24
+ end
25
+
26
+ def debugonce(msg)
27
+ if @@debug_messages[msg].nil?
28
+ self.debug(msg)
29
+ @@debug_messages[msg] = true
30
+ end
31
+ end
32
+
33
+ def log_exception(exception, message = nil)
34
+ message = exception.message if message.nil?
35
+
36
+ output = []
37
+ output << message
38
+ output.concat(exception.backtrace)
39
+
40
+ self.warn(output.flatten.join("\n"))
41
+ end
42
+ end
@@ -0,0 +1,46 @@
1
+ require 'concurrent'
2
+
3
+ require 'rfacter'
4
+
5
+ # Non-nullable thread local variable
6
+ #
7
+ # A Subclass of Concurrent::ThreadLocalVar that raises a NameError
8
+ # if de-referenced to `nil`. This allows the creation of variables
9
+ # that must always be bound to a specific value before use.
10
+ #
11
+ # @since 0.1.0
12
+ class RFacter::Util::NonNullable < Concurrent::ThreadLocalVar
13
+ # @param err_message [String] The error message to raise if
14
+ # the instance is de-referenced to a `nil` value.
15
+ def initialize(default = nil, err_message:, &default_block)
16
+ @err_message = err_message
17
+ super(default, &default_block)
18
+ end
19
+
20
+ # Private reference to the superclass `value` method that won't
21
+ # raise an error. Allows the `bind` method to re-set the variable
22
+ # to nil at the end of an operation.
23
+ alias_method :nillable_value, :value
24
+ private :nillable_value
25
+
26
+ def bind(value, &block)
27
+ if block_given?
28
+ old_value = nillable_value
29
+ begin
30
+ self.value = value
31
+ yield
32
+ ensure
33
+ self.value = old_value
34
+ end
35
+ end
36
+ end
37
+
38
+ # @raise [NameError] when de-referenced to `nil`.
39
+ def value
40
+ result = super
41
+
42
+ raise(NameError, @err_message) if result.nil?
43
+
44
+ result
45
+ end
46
+ end
@@ -0,0 +1,96 @@
1
+ require 'rfacter'
2
+
3
+ module RFacter
4
+ module Util
5
+ module Normalization
6
+ class NormalizationError < StandardError; end
7
+
8
+ VALID_TYPES = [Integer, Float, TrueClass, FalseClass, NilClass, String, Array, Hash]
9
+
10
+ module_function
11
+
12
+ # Recursively normalize the given data structure
13
+ #
14
+ # @api public
15
+ # @raise [NormalizationError] If the data structure contained an invalid element.
16
+ # @return [void]
17
+ def normalize(value)
18
+ case value
19
+ when Integer, Float, TrueClass, FalseClass, NilClass
20
+ value
21
+ when String
22
+ normalize_string(value)
23
+ when Array
24
+ normalize_array(value)
25
+ when Hash
26
+ normalize_hash(value)
27
+ else
28
+ raise NormalizationError, "Expected #{value} to be one of #{VALID_TYPES.inspect}, but was #{value.class}"
29
+ end
30
+ end
31
+
32
+ # @!method normalize_string(value)
33
+ #
34
+ # Attempt to normalize and validate the given string.
35
+ #
36
+ # On Ruby 1.8 the string is checked by stripping out all non UTF-8
37
+ # characters and comparing the converted string to the original. If they
38
+ # do not match then the string is considered invalid.
39
+ #
40
+ # On Ruby 1.9+, the string is validate by checking that the string encoding
41
+ # is UTF-8 and that the string content matches the encoding. If the string
42
+ # is not an expected encoding then it is converted to UTF-8.
43
+ #
44
+ # @api public
45
+ # @raise [NormalizationError] If the string used an unsupported encoding or did not match its encoding
46
+ # @param value [String]
47
+ # @return [void]
48
+
49
+ if RUBY_VERSION =~ /^1\.8/
50
+ require 'iconv'
51
+
52
+ def normalize_string(value)
53
+ converted = Iconv.conv('UTF-8//IGNORE', 'UTF-8', value)
54
+ if converted != value
55
+ raise NormalizationError, "String #{value.inspect} is not valid UTF-8"
56
+ end
57
+ value
58
+ end
59
+ else
60
+ def normalize_string(value)
61
+ value = value.encode(Encoding::UTF_8)
62
+
63
+ unless value.valid_encoding?
64
+ raise NormalizationError, "String #{value.inspect} doesn't match the reported encoding #{value.encoding}"
65
+ end
66
+
67
+ value
68
+ rescue EncodingError
69
+ raise NormalizationError, "String encoding #{value.encoding} is not UTF-8 and could not be converted to UTF-8"
70
+ end
71
+ end
72
+
73
+ # Validate all elements of the array.
74
+ #
75
+ # @api public
76
+ # @raise [NormalizationError] If one of the elements failed validation
77
+ # @param value [Array]
78
+ # @return [void]
79
+ def normalize_array(value)
80
+ value.collect do |elem|
81
+ normalize(elem)
82
+ end
83
+ end
84
+
85
+ # Validate all keys and values of the hash.
86
+ #
87
+ # @api public
88
+ # @raise [NormalizationError] If one of the keys or values failed normalization
89
+ # @param value [Hash]
90
+ # @return [void]
91
+ def normalize_hash(value)
92
+ Hash[value.collect { |k, v| [ normalize(k), normalize(v) ] } ]
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,163 @@
1
+ require 'forwardable'
2
+
3
+ require 'rfacter'
4
+ require_relative '../config'
5
+
6
+ # This represents a fact resolution. A resolution is a concrete
7
+ # implementation of a fact. A single fact can have many resolutions and
8
+ # the correct resolution will be chosen at runtime. Each time
9
+ # {Facter.add} is called, a new resolution is created and added to the
10
+ # set of resolutions for the fact named in the call. Each resolution
11
+ # has a {#has_weight weight}, which defines its priority over other
12
+ # resolutions, and a set of {#confine _confinements_}, which defines the
13
+ # conditions under which it will be chosen. All confinements must be
14
+ # satisfied for a fact to be considered _suitable_.
15
+ #
16
+ # @api public
17
+ class RFacter::Util::Resolution
18
+ require_relative '../dsl'
19
+ require_relative '../core/resolvable'
20
+ require_relative '../core/suitable'
21
+ extend Forwardable
22
+
23
+ instance_delegate([:logger] => :@config)
24
+
25
+ # @api private
26
+ attr_accessor :code
27
+ attr_writer :value
28
+
29
+ include RFacter::Core::Resolvable
30
+ include RFacter::Core::Suitable
31
+
32
+ # @!attribute [rw] name
33
+ # The name of this resolution. The resolution name should be unique with
34
+ # respect to the given fact.
35
+ # @return [String]
36
+ # @api public
37
+ attr_accessor :name
38
+
39
+ # @!attribute [r] fact
40
+ # @return [Facter::Util::Fact]
41
+ # @api private
42
+ attr_reader :fact
43
+
44
+ def which(command)
45
+ ::RFacter::DSL::Facter::Core::Execution.which(command)
46
+ end
47
+
48
+ def exec(command)
49
+ ::RFacter::DSL::Facter::Core::Execution.exec(command)
50
+ end
51
+
52
+ # Create a new resolution mechanism.
53
+ #
54
+ # @param name [String] The name of the resolution.
55
+ # @return [void]
56
+ #
57
+ # @api private
58
+ def initialize(name, fact, config: RFacter::Config.config, **options)
59
+ @name = name
60
+ @fact = fact
61
+ @config = config
62
+ @confines = []
63
+ @value = nil
64
+ @timeout = 0
65
+ @weight = nil
66
+ end
67
+
68
+ def resolution_type
69
+ :simple
70
+ end
71
+
72
+ # Evaluate the given block in the context of this resolution. If a block has
73
+ # already been evaluated emit a warning to that effect.
74
+ #
75
+ # @return [void]
76
+ def evaluate(&block)
77
+ if @last_evaluated
78
+ msg = "Already evaluated #{@name}"
79
+ msg << " at #{@last_evaluated}" if msg.is_a? String
80
+ msg << ", reevaluating anyways"
81
+ logger.warn msg
82
+ end
83
+
84
+ instance_eval(&block)
85
+
86
+ # Ruby 1.9+ provides the source location of procs which can provide useful
87
+ # debugging information if a resolution is being evaluated twice. Since 1.8
88
+ # doesn't support this we opportunistically provide this information.
89
+ if block.respond_to? :source_location
90
+ @last_evaluated = block.source_location.join(':')
91
+ else
92
+ @last_evaluated = true
93
+ end
94
+ end
95
+
96
+ def set_options(options)
97
+ if options[:name]
98
+ @name = options.delete(:name)
99
+ end
100
+
101
+ if options.has_key?(:value)
102
+ @value = options.delete(:value)
103
+ end
104
+
105
+ if options.has_key?(:timeout)
106
+ @timeout = options.delete(:timeout)
107
+ end
108
+
109
+ if options.has_key?(:weight)
110
+ @weight = options.delete(:weight)
111
+ end
112
+
113
+ if not options.keys.empty?
114
+ raise ArgumentError, "Invalid resolution options #{options.keys.inspect}"
115
+ end
116
+ end
117
+
118
+ # Sets the code block or external program that will be evaluated to
119
+ # get the value of the fact.
120
+ #
121
+ # @return [void]
122
+ #
123
+ # @overload setcode(string)
124
+ # Sets an external program to call to get the value of the resolution
125
+ # @param [String] string the external program to run to get the
126
+ # value
127
+ #
128
+ # @overload setcode(&block)
129
+ # Sets the resolution's value by evaluating a block at runtime
130
+ # @param [Proc] block The block to determine the resolution's value.
131
+ # This block is run when the fact is evaluated. Errors raised from
132
+ # inside the block are rescued and printed to stderr.
133
+ #
134
+ # @api public
135
+ def setcode(string = nil, &block)
136
+ if string
137
+ @code = Proc.new do
138
+ output = exec(string)
139
+ if output.nil? or output.empty?
140
+ nil
141
+ else
142
+ output
143
+ end
144
+ end
145
+ elsif block_given?
146
+ @code = block
147
+ else
148
+ raise ArgumentError, "You must pass either code or a block"
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ def resolve_value
155
+ if @value
156
+ @value
157
+ elsif @code.nil?
158
+ nil
159
+ elsif @code
160
+ @code.call()
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,110 @@
1
+ require 'rfacter'
2
+
3
+ module RFacter
4
+ module Util
5
+ # A util module for facter containing helper methods
6
+ module Values
7
+ module_function
8
+
9
+ class DeepFreezeError < StandardError; end
10
+
11
+ # Duplicate and deeply freeze a given data structure
12
+ #
13
+ # @param value [Object] The structure to freeze
14
+ # @return [void]
15
+ def deep_freeze(value)
16
+ case value
17
+ when Numeric, Symbol, TrueClass, FalseClass, NilClass
18
+ # These are immutable values, we can safely ignore them
19
+ value
20
+ when String
21
+ value.dup.freeze
22
+ when Array
23
+ value.map do |entry|
24
+ deep_freeze(entry)
25
+ end.freeze
26
+ when Hash
27
+ value.inject({}) do |hash, (key, value)|
28
+ hash[deep_freeze(key)] = deep_freeze(value)
29
+ hash
30
+ end.freeze
31
+ else
32
+ raise DeepFreezeError, "Cannot deep freeze #{value}:#{value.class}"
33
+ end
34
+ end
35
+
36
+ class DeepMergeError < StandardError; end
37
+
38
+ # Perform a deep merge of two nested data structures.
39
+ #
40
+ # @param left [Object]
41
+ # @param right [Object]
42
+ # @param path [Array<String>] The traversal path followed when merging nested hashes
43
+ #
44
+ # @return [Object] The merged data structure.
45
+ def deep_merge(left, right, path = [], &block)
46
+ ret = nil
47
+
48
+ if left.is_a? Hash and right.is_a? Hash
49
+ ret = left.merge(right) do |key, left_val, right_val|
50
+ path.push(key)
51
+ merged = deep_merge(left_val, right_val, path)
52
+ path.pop
53
+ merged
54
+ end
55
+ elsif left.is_a? Array and right.is_a? Array
56
+ ret = left.dup.concat(right)
57
+ elsif right.nil?
58
+ ret = left
59
+ elsif left.nil?
60
+ ret = right
61
+ elsif left.nil? and right.nil?
62
+ ret = nil
63
+ else
64
+ msg = "Cannot merge #{left.inspect}:#{left.class} and #{right.inspect}:#{right.class}"
65
+ if not path.empty?
66
+ msg << " at root"
67
+ msg << path.map { |part| "[#{part.inspect}]" }.join
68
+ end
69
+ raise DeepMergeError, msg
70
+ end
71
+
72
+ ret
73
+ end
74
+
75
+ def convert(value)
76
+ value = value.to_s if value.is_a?(Symbol)
77
+ value = value.downcase if value.is_a?(String)
78
+ value
79
+ end
80
+
81
+ # Flatten the given data structure to something that's suitable to return
82
+ # as flat facts.
83
+ #
84
+ # @param path [String] The fact path to be prefixed to the given value.
85
+ # @param structure [Object] The data structure to flatten. Nested hashes
86
+ # will be recursively flattened, everything else will be returned as-is.
87
+ #
88
+ # @return [Hash] The given data structure prefixed with the given path
89
+ def flatten_structure(path, structure)
90
+ results = {}
91
+
92
+ if structure.is_a? Hash
93
+ structure.each_pair do |name, value|
94
+ new_path = "#{path}_#{name}".gsub(/\-|\//, '_')
95
+ results.merge! flatten_structure(new_path, value)
96
+ end
97
+ elsif structure.is_a? Array
98
+ structure.each_with_index do |value, index|
99
+ new_path = "#{path}_#{index}"
100
+ results.merge! flatten_structure(new_path, value)
101
+ end
102
+ else
103
+ results[path] = structure
104
+ end
105
+
106
+ results
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,3 @@
1
+ module RFacter
2
+ VERSION = '0.0.1'.freeze
3
+ end
data/lib/rfacter.rb ADDED
@@ -0,0 +1,6 @@
1
+ module RFacter;end
2
+ module RFacter::Config;end
3
+ module RFacter::Core;end
4
+ module RFacter::Util;end
5
+
6
+ require_relative 'rfacter/version'
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rfacter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Charlie Sharpsteen
8
+ - Puppet Labs
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-05-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: train
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 0.23.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 0.23.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: concurrent-ruby
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: inch
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '0.7'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '0.7'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.1'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.1'
70
+ description: |
71
+ RFacter is a library for collecting facts from remote system(s) by executing
72
+ commands over transports such as SSH and WinRM.
73
+ email: source@sharpsteen.net
74
+ executables:
75
+ - rfacter
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - bin/rfacter
80
+ - lib/rfacter.rb
81
+ - lib/rfacter/cli.rb
82
+ - lib/rfacter/config.rb
83
+ - lib/rfacter/config/settings.rb
84
+ - lib/rfacter/core/aggregate.rb
85
+ - lib/rfacter/core/directed_graph.rb
86
+ - lib/rfacter/core/resolvable.rb
87
+ - lib/rfacter/core/suitable.rb
88
+ - lib/rfacter/dsl.rb
89
+ - lib/rfacter/facts/kernel.rb
90
+ - lib/rfacter/facts/kernelmajversion.rb
91
+ - lib/rfacter/facts/kernelrelease.rb
92
+ - lib/rfacter/facts/kernelversion.rb
93
+ - lib/rfacter/facts/networking.rb
94
+ - lib/rfacter/facts/os.rb
95
+ - lib/rfacter/node.rb
96
+ - lib/rfacter/util/collection.rb
97
+ - lib/rfacter/util/confine.rb
98
+ - lib/rfacter/util/fact.rb
99
+ - lib/rfacter/util/loader.rb
100
+ - lib/rfacter/util/logger.rb
101
+ - lib/rfacter/util/non_nullable.rb
102
+ - lib/rfacter/util/normalization.rb
103
+ - lib/rfacter/util/resolution.rb
104
+ - lib/rfacter/util/values.rb
105
+ - lib/rfacter/version.rb
106
+ homepage: https://github.com/Sharpie/rfacter
107
+ licenses:
108
+ - Apache-2.0
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 2.1.0
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.2.5
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Reduced, Remote-enabled, Ruby fork of Facter 2.x
130
+ test_files: []