rfacter 0.0.1

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