rfacter 0.0.1 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 57a9d18faa5cfba3610ad4e136cc0f6b67674f7e
4
- data.tar.gz: ddc6e4a94f5cb61e2ff6133c46d1eb1bda340eed
3
+ metadata.gz: e7bc8b832480542b3e326f4f99389ea4d2b9968d
4
+ data.tar.gz: 37bad144b7ffbef453ad397ea39ed7ca89c1e43a
5
5
  SHA512:
6
- metadata.gz: e2633c5de708b220c5d8cb5bb39aadb635cce530fe9d7a5d1c228860c82f3f698c54ca4d45f33850fd796914e6a53b69f96f3efaa2908583eb6b3cd2a585c20f
7
- data.tar.gz: b6ebc55e51bfb161ef8363e3f1381e9b4ce5d37ef21bfc27eee45b101c055c2a279a747bd36049f2b0c59c5752de9fa8426d4af7931c1e7f9646576a6f198244
6
+ metadata.gz: afef987a44e33cc8c83134de30694a0a3591f64984aac19906cbe35e27f090635c67af8202271af9b1f3ccfeafce07630690de3678eeafda008ec20e5336347e
7
+ data.tar.gz: fea2ba928eb78d00e736e35b53bd2f1106223aa612a4371bfe36bcb9e9d1b5731bfe05c5bc27aafc3d38769e9a2da79c27c01cb59d38091edb850219630f3127
@@ -1,3 +1,4 @@
1
+ # Reduced, Remote-enabled, Ruby fork of Facter
1
2
  module RFacter;end
2
3
  module RFacter::Config;end
3
4
  module RFacter::Core;end
@@ -5,8 +5,13 @@ require 'rfacter'
5
5
 
6
6
  require_relative 'config'
7
7
  require_relative 'node'
8
+ require_relative 'factset'
8
9
  require_relative 'util/collection'
9
10
 
11
+ # RFacter Command Line Interface module
12
+ #
13
+ # @api public
14
+ # @since 0.1.0
10
15
  module RFacter::CLI
11
16
  extend SingleForwardable
12
17
 
@@ -20,31 +25,17 @@ module RFacter::CLI
20
25
  @config.nodes['localhost'] = RFacter::Node.new('localhost')
21
26
  end
22
27
 
23
- logger.info('cli::run') { "Configured nodes: #{@config.nodes.values.map(&:hostname)}" }
28
+ logger.info('cli::run') { "Configured nodes: #{@config.nodes.values.map(&:id)}" }
24
29
 
25
- collection = RFacter::Util::Collection.new
26
- collection.load_all
30
+ factset = RFacter::Factset.new(@config.nodes.values)
27
31
 
28
- facts = @config.nodes.values.inject(Hash.new) do |h, node|
29
- node_facts = if names.empty?
30
- collection.to_hash(node)
31
- else
32
- names.inject(Hash.new) do |n, name|
33
- n[name] = collection.value(name, node)
34
- n
35
- end
36
- end
32
+ node_facts = if names.empty?
33
+ factset.to_hash
34
+ else
35
+ factset.value(names)
36
+ end
37
37
 
38
- # TODO: Implement proper per-node Fact caching so that we don't just
39
- # reset the colleciton on each loop.
40
- collection.flush
41
-
42
- h[node.hostname] = node_facts
43
- h
44
- end
45
-
46
-
47
- puts JSON.pretty_generate(facts)
38
+ puts JSON.pretty_generate(node_facts)
48
39
 
49
40
  exit 0
50
41
  end
@@ -13,6 +13,7 @@ require_relative 'node'
13
13
  # and contains methods for initializing the settings instance from
14
14
  # various sources.
15
15
  #
16
+ # @api public
16
17
  # @since 0.1.0
17
18
  module RFacter::Config
18
19
  # Return global configuration
@@ -75,6 +76,10 @@ module RFacter::Config
75
76
  settings.logger.level = Logger::DEBUG
76
77
  end
77
78
 
79
+ parser.on('--profile', 'Profile fact resolution times.') do
80
+ settings.profile = true
81
+ end
82
+
78
83
  parser.on('-n', '--node', '=MANDATORY', URI, 'Add a node by URI.') do |uri|
79
84
  node = RFacter::Node.new(uri)
80
85
  settings.nodes[node.hostname] = node
@@ -6,6 +6,7 @@ require_relative '../util/logger'
6
6
  # Instances of this class hold top-level configuration values and shared
7
7
  # service objects such as loggers.
8
8
  #
9
+ # @api public
9
10
  # @since 0.1.0
10
11
  class RFacter::Config::Settings
11
12
  # Access the logger instance
@@ -22,10 +23,16 @@ class RFacter::Config::Settings
22
23
  # schemes to use when contacting them.
23
24
  attr_reader :nodes
24
25
 
26
+ # A boolean switch for enabling execution profiling
27
+ #
28
+ # @return [Boolean] Defaults to false.
29
+ attr_accessor :profile
30
+
25
31
  def initialize(**options)
26
32
  @logger = RFacter::Util::Logger.new($stderr)
27
33
  @logger.level = Logger::WARN
28
34
 
35
+ @profile = false
29
36
  @nodes = Hash.new
30
37
  end
31
38
  end
@@ -8,12 +8,12 @@ require_relative '../config'
8
8
  # Aggregates are evaluated in two parts: generating individual chunks and then
9
9
  # aggregating all chunks together. Each chunk is a block of code that generates
10
10
  # a value, and may depend on other chunks when it runs. After all chunks have
11
- # been evaluated they are passed to the aggregate block as Hash<name, result>.
11
+ # been evaluated they are passed to the aggregate block as `Hash<name, result>`.
12
12
  # The aggregate block converts the individual chunks into a single value that is
13
13
  # returned as the final value of the aggregate.
14
14
  #
15
- # @api public
16
- # @since 2.0.0
15
+ # @api private
16
+ # @since 0.1.0
17
17
  class RFacter::Core::Aggregate
18
18
  require_relative 'directed_graph'
19
19
  require_relative 'resolvable'
@@ -32,19 +32,17 @@ class RFacter::Core::Aggregate
32
32
  attr_reader :name
33
33
 
34
34
  # @!attribute [r] deps
35
- # @api private
36
- # @return [Facter::Core::DirectedGraph]
35
+ # @return [RFacter::Core::DirectedGraph]
37
36
  attr_reader :deps
38
37
 
39
38
  # @!attribute [r] confines
40
- # @return [Array<Facter::Core::Confine>] An array of confines restricting
39
+ # @return [Array<RFacter::Core::Confine>] An array of confines restricting
41
40
  # this to a specific platform
42
- # @see Facter::Core::Suitable
41
+ # @see RFacter::Core::Suitable
43
42
  attr_reader :confines
44
43
 
45
44
  # @!attribute [r] fact
46
- # @return [Facter::Util::Fact]
47
- # @api private
45
+ # @return [RFacter::Util::Fact]
48
46
  attr_reader :fact
49
47
 
50
48
  def initialize(name, fact, config: RFacter::Config.config, **options)
@@ -83,8 +81,6 @@ class RFacter::Core::Aggregate
83
81
 
84
82
  # Define a new chunk for the given aggregate
85
83
  #
86
- # @api public
87
- #
88
84
  # @example Defining a chunk with no dependencies
89
85
  # aggregate.chunk(:mountpoints) do
90
86
  # # generate mountpoint information
@@ -120,8 +116,6 @@ class RFacter::Core::Aggregate
120
116
 
121
117
  # Define how all chunks should be combined
122
118
  #
123
- # @api public
124
- #
125
119
  # @example Merge all chunks
126
120
  # aggregate.aggregate do |chunks|
127
121
  # final_result = {}
@@ -160,7 +154,7 @@ class RFacter::Core::Aggregate
160
154
 
161
155
  # Evaluate the results of this aggregate.
162
156
  #
163
- # @see Facter::Core::Resolvable#value
157
+ # @see RFacter::Core::Resolvable#value
164
158
  # @return [Object]
165
159
  def resolve_value
166
160
  chunk_results = run_chunks()
@@ -5,6 +5,12 @@ require 'rfacter'
5
5
 
6
6
  module RFacter
7
7
  module Core
8
+ # Directed graph for sorting aggregate chunk dependencies
9
+ #
10
+ # @see RFacter::Core::Aggregate
11
+ #
12
+ # @api private
13
+ # @since 0.1.0
8
14
  class DirectedGraph < Hash
9
15
  include TSort
10
16
 
@@ -9,6 +9,9 @@ require_relative '../util/normalization'
9
9
  # Classes including this mixin should implement at #name method describing
10
10
  # the value being resolved and a #resolve_value that actually executes the code
11
11
  # to resolve the value.
12
+ #
13
+ # @api private
14
+ # @since 0.1.0
12
15
  module RFacter::Core::Resolvable
13
16
 
14
17
  # The timeout, in seconds, for evaluating this resolution.
@@ -19,11 +22,11 @@ module RFacter::Core::Resolvable
19
22
  # Return the timeout period for resolving a value.
20
23
  # (see #timeout)
21
24
  # @return [Numeric]
22
- # @comment requiring 'timeout' stdlib class causes Object#timeout to be
23
- # defined which delegates to Timeout.timeout. This method may potentially
24
- # overwrite the #timeout attr_reader on this class, so we define #limit to
25
- # avoid conflicts.
26
25
  def limit
26
+ # requiring 'timeout' stdlib class causes Object#timeout to be defined
27
+ # which delegates to Timeout.timeout. This method may potentially overwrite
28
+ # the #timeout attr_reader on this class, so we define #limit to avoid
29
+ # conflicts.
27
30
  @timeout || 0
28
31
  end
29
32
 
@@ -39,8 +42,7 @@ module RFacter::Core::Resolvable
39
42
  # Please see the Solaris zones fact for an example of how this feature may be
40
43
  # used.
41
44
  #
42
- # @see Facter::Util::Fact#flush
43
- # @see Facter::Util::Resolution#flush
45
+ # @see RFacter::Util::Fact#flush
44
46
  #
45
47
  # @api public
46
48
  def on_flush(&block)
@@ -48,10 +50,9 @@ module RFacter::Core::Resolvable
48
50
  end
49
51
 
50
52
  ##
51
- # flush executes the block, if any, stored by the {on_flush} method
53
+ # flush executes the block, if any, stored by the {#on_flush} method
52
54
  #
53
- # @see Facter::Util::Fact#flush
54
- # @see Facter::Util::Resolution#on_flush
55
+ # @see RFacter::Util::Fact#flush
55
56
  #
56
57
  # @api private
57
58
  def flush
@@ -82,13 +83,16 @@ module RFacter::Core::Resolvable
82
83
  private
83
84
 
84
85
  def with_timing
85
- starttime = Time.now.to_f
86
-
87
- yield
86
+ unless @config.profile
87
+ yield
88
+ else
89
+ starttime = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
90
+ yield
91
+ finishtime = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
88
92
 
89
- finishtime = Time.now.to_f
90
- ms = (finishtime - starttime) * 1000
91
- #::Facter.show_time "#{qualified_name}: #{"%.2f" % ms}ms"
93
+ elapsed = (finishtime - starttime)
94
+ logger.info { "#{qualified_name}: #{"%.2f" % elapsed}ms" }
95
+ end
92
96
  end
93
97
 
94
98
  def qualified_name
@@ -4,7 +4,10 @@ require 'rfacter'
4
4
  # certain platforms and determining the run precedence of these objects.
5
5
  #
6
6
  # Classes that include the Suitable mixin should define a `#confines` method
7
- # that returns an Array of zero or more Facter::Util::Confine objects.
7
+ # that returns an Array of zero or more {RFacter::Util::Confine} objects.
8
+ #
9
+ # @api private
10
+ # @since 0.1.0
8
11
  module RFacter::Core::Suitable
9
12
  require_relative '../util/confine'
10
13
 
@@ -18,8 +21,6 @@ module RFacter::Core::Suitable
18
21
  # @param weight [Integer] the weight of this resolution
19
22
  #
20
23
  # @return [void]
21
- #
22
- # @api public
23
24
  def has_weight(weight)
24
25
  @weight = weight
25
26
  end
@@ -29,8 +30,6 @@ module RFacter::Core::Suitable
29
30
  #
30
31
  # @return [void]
31
32
  #
32
- # @api public
33
- #
34
33
  # @overload confine(confines)
35
34
  # Confine a fact to a specific fact value or values. This form takes a
36
35
  # hash of fact names and values. Every fact must match the values given for
@@ -40,7 +39,7 @@ module RFacter::Core::Suitable
40
39
  # @param [Hash{String,Symbol=>String,Array<String>}] confines set of facts identified by the hash keys whose
41
40
  # fact value must match the argument value.
42
41
  # @example Confining to Linux
43
- # Facter.add(:powerstates) do
42
+ # RFacter.add(:powerstates) do
44
43
  # # This resolution only makes sense on linux systems
45
44
  # confine :kernel => "Linux"
46
45
  # setcode do
@@ -56,7 +55,7 @@ module RFacter::Core::Suitable
56
55
  # @param [Proc] block determines the suitability of the fact. If the block
57
56
  # evaluates to `false` or `nil` then the confined fact will not be
58
57
  # evaluated.
59
- # @yield [value] the value of the fact identified by {confines}
58
+ # @yield [value] the value of the fact identified by `confines`
60
59
  # @example Confine the fact to a host with an ipaddress in a specific
61
60
  # subnet
62
61
  # confine :ipaddress do |addr|
@@ -95,8 +94,6 @@ module RFacter::Core::Suitable
95
94
  # specific resolution wins over a less specific one).
96
95
  #
97
96
  # @return [Integer] the weight of this resolution
98
- #
99
- # @api private
100
97
  def weight
101
98
  if @weight
102
99
  @weight
@@ -106,8 +103,6 @@ module RFacter::Core::Suitable
106
103
  end
107
104
 
108
105
  # Is this resolution mechanism suitable on the system in question?
109
- #
110
- # @api private
111
106
  def suitable?
112
107
  @confines.all? { |confine| confine.true? }
113
108
  end
@@ -7,9 +7,10 @@ require_relative 'util/non_nullable'
7
7
  # Facter compatibility layer
8
8
  #
9
9
  # This module exists to provide compatibility shims for Facter DSL methods as
10
- # exposed by the Facter 3.0 Ruby API. Any fact source code that is executed
11
- # within the {RFacter::Util} namespace via `instance_eval` should pick up on
12
- # these shims. The methods in this module should never be called directly.
10
+ # exposed by the Facter 3 Ruby Extension API. Any fact source code that is
11
+ # loaded into a {RFacter::Util::Collection} instance via `instance_eval`
12
+ # should pick up on these shims. The methods in this module should never be
13
+ # called outside of files that define custom facts.
13
14
  #
14
15
  # However, lexical scope is a tricky thing, so "should" is the operative word
15
16
  # here.
@@ -60,7 +61,7 @@ EOS
60
61
  # of {RFacter::Util::Fact} and {Facter::Util::Resolution} can be
61
62
  # supplied here
62
63
  # @option options [Integer] :timeout set the
63
- # {RFacter::Util::Resolution#timeout timeout} for this resolution
64
+ # {RFacter::Core::Resolvable#timeout timeout} for this resolution
64
65
  # @param block [Proc] a block defining a fact resolution
65
66
  #
66
67
  # @return [RFacter::Util::Fact] the fact object, which includes any previously
@@ -104,7 +105,7 @@ EOS
104
105
  #
105
106
  # @return [RFacter::Util::Fact] The fact that was defined
106
107
  #
107
- # @see {RFacter::Util::Collection#define_fact}
108
+ # @see RFacter::Util::Collection#define_fact
108
109
  def self.define_fact(name, options = {}, &block)
109
110
  COLLECTION.value.define_fact(name, options, &block)
110
111
  end
@@ -116,7 +117,7 @@ EOS
116
117
  #
117
118
  # @return [void]
118
119
  def self.each
119
- COLLECTION.value.each(NODE.value) do |*args|
120
+ COLLECTION.value.each do |*args|
120
121
  yield(*args)
121
122
  end
122
123
  end
@@ -185,7 +186,7 @@ EOS
185
186
  #
186
187
  # @return [Hash{String => Object}] the hash of fact names and values
187
188
  def self.to_hash
188
- COLLECTION.value.to_hash(NODE.value)
189
+ COLLECTION.value.to_hash
189
190
  end
190
191
 
191
192
  # Gets the value for a fact.
@@ -195,7 +196,7 @@ EOS
195
196
  # @return [Object, nil] the value of the fact, or nil if no fact is
196
197
  # found
197
198
  def self.value(name)
198
- COLLECTION.value.value(name, NODE.value)
199
+ COLLECTION.value.value(name)
199
200
  end
200
201
 
201
202
  # Returns the current RFacter version
@@ -304,11 +305,11 @@ EOS
304
305
  # Facter::Util DSL methods
305
306
  module Util
306
307
  require_relative 'util/fact'
307
- # (see RFacter::Util::Fact)
308
+ # @see RFacter::Util::Fact
308
309
  Fact = ::RFacter::Util::Fact
309
310
 
310
311
  require_relative 'util/resolution'
311
- # (see RFacter::Util::Resolution)
312
+ # @see RFacter::Util::Resolution
312
313
  Resolution = ::RFacter::Util::Resolution
313
314
 
314
315
  # Methods for interacting with remote files.
@@ -40,21 +40,56 @@ Facter.add(:os, :type => :aggregate) do
40
40
  # stats and reads, so multiple file ops only incur a cost once. However,
41
41
  # these ops are all going over the network, so we should probably
42
42
  # prioritize RedHat and SuSE detection over less common Linuxen in order to
43
- # cut down on network chatter that will be useless in most cases. Also,
44
- # might be worth adding /etc/os-release detection at the front of the list
45
- # since that's the new hotness from systemd standardization.
43
+ # cut down on network chatter that will be useless in most cases.
46
44
  operatingsystem = nil
47
45
 
48
- operatingsystem = if Facter.value(:kernel) == "GNU/kFreeBSD"
49
- "GNU/kFreeBSD"
50
- elsif Facter::Util::FileRead.exists?('/etc/debian_version')
51
- case Facter::Core::Execution.exec('lsb_release -i')
52
- when /Ubuntu/i
53
- 'Ubuntu'
54
- when /LinuxMint/i
55
- 'LinuxMint'
56
- else
57
- 'Debian'
46
+ # Determine OS name from /etc/os-release if it exists.
47
+ #
48
+ # see: https://www.freedesktop.org/software/systemd/man/os-release.html
49
+ if Facter::Util::FileRead.exists?('/etc/os-release')
50
+ os_release = Facter::Util::FileRead.read('/etc/os-release')
51
+ # NOTE: The ID field is used instead of NAME as ID was explicitly
52
+ # designed to be parsed by scripts whereas NAME is for human consumption.
53
+ os_id = os_release.lines.find {|l| l.start_with?('ID=')}
54
+
55
+ unless os_id.nil?
56
+ operatingsystem = case os_id
57
+ when /debian/i
58
+ 'Debian'
59
+ when /ubuntu/i
60
+ 'Ubuntu'
61
+ when /opensuse/i
62
+ 'OpenSuSE'
63
+ when /sles/i
64
+ 'SLES'
65
+ when /fedora/i
66
+ 'Fedora'
67
+ when /centos/i
68
+ 'CentOS'
69
+ when /ol/i
70
+ 'OracleLinux'
71
+ when /rhel/i
72
+ 'RedHat'
73
+ when /amzn/i
74
+ 'Amazon'
75
+ else
76
+ os_id.scan(/^(?:\w+)=[\"']?(.+?)[\"']?$/).flatten.first
77
+ end
78
+ end
79
+ end
80
+
81
+ if operatingsystem.nil?
82
+ operatingsystem = if Facter.value(:kernel) == "GNU/kFreeBSD"
83
+ "GNU/kFreeBSD"
84
+ elsif Facter::Util::FileRead.exists?('/etc/debian_version')
85
+ case Facter::Core::Execution.exec('lsb_release -i')
86
+ when /Ubuntu/i
87
+ 'Ubuntu'
88
+ when /LinuxMint/i
89
+ 'LinuxMint'
90
+ else
91
+ 'Debian'
92
+ end
58
93
  end
59
94
  end
60
95
 
@@ -0,0 +1,116 @@
1
+ require 'forwardable'
2
+
3
+ require 'rfacter'
4
+
5
+ require_relative 'config'
6
+ require_relative 'node'
7
+ require_relative 'util/collection'
8
+
9
+ # A class that can retrieve facts from several nodes
10
+ #
11
+ # A factset joins instances of {RFacter::Node} to {RFacter::Util::Collection}
12
+ # and enables parallel and asynchronous resolution of fact values across
13
+ # several nodes. Supports retrieving single facts asynchronously via {#fetch}
14
+ # and in a blocking fashion via {#value}. All facts can be retrieved
15
+ # asynchronously via {#fetch_all} and in a blocking fashion via {#to_hash}.
16
+ #
17
+ # @api public
18
+ # @since 0.1.0
19
+ class RFacter::Factset
20
+ extend Forwardable
21
+
22
+ instance_delegate([:logger] => :@config)
23
+
24
+ # Returns a new instance of Factset
25
+ #
26
+ # @param nodes [Array<RFacter::Node>] An array of node objects to collect
27
+ # facts from.
28
+ def initialize(nodes, config: RFacter::Config.config, **opts)
29
+ @config = config
30
+
31
+ @collections = nodes.each_with_object({}) do |node, hash|
32
+ hash[node.id] = RFacter::Util::Collection.new(node)
33
+ end
34
+ end
35
+
36
+ # Asynchronously fetch the value of a fact from each node
37
+ #
38
+ # This method spawns a background thread per node which resolves the value
39
+ # of a fact specified by `query`.
40
+ #
41
+ # @param queries [Array<String>] The names of the facts to fetch.
42
+ #
43
+ # @return [Concurrent::Future{String => Hash}]
44
+ # A future that will return a hash mapping the node id to a hash containing
45
+ # the resolved facts when `value` is called.
46
+ def fetch(queries)
47
+ queries = Array(queries)
48
+ # Spawn async lookups in the background for each node.
49
+ futures = @collections.each_with_object({}) do |(name, collection), hash|
50
+ hash[name] = {}
51
+ queries.each do |query|
52
+ hash[name][query] = collection.async.value(query)
53
+ end
54
+ end
55
+
56
+ # Return a future with the resolved values.
57
+ Concurrent::Future.execute do
58
+ futures.each_with_object({}) do |(name, ivars), hash|
59
+ hash[name] = ivars.each_with_object({}) do |(query, ivar), results|
60
+ # TODO: Add exception handling for failed futures.
61
+ results[query] = ivar.value
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ # Fetch the value of a fact from each node
68
+ #
69
+ # This method calls {#fetch} and then blocks until the result is available.
70
+ #
71
+ # @return [Hash{String => Hash}]
72
+ # A hash mapping the node id to a hash containing the resolved facts.
73
+ def value(queries)
74
+ fetch(queries).value
75
+ end
76
+
77
+ # Asynchronously fetch all facts from each node
78
+ #
79
+ # This method spawns a background thread per node which resolves all
80
+ # fact values for each node.
81
+ #
82
+ # @return [Concurrent::Future{String => Hash}]
83
+ # A future that will return a hash mapping the node id to a hash containing
84
+ # the resolved facts when `value` is called.
85
+ def fetch_all
86
+ futures = @collections.each_with_object({}) do |(name, collection), hash|
87
+ collection.async.load_all
88
+ hash[name] = collection.async.to_hash
89
+ end
90
+
91
+ Concurrent::Future.execute do
92
+ futures.each_with_object({}) do |(name, future), hash|
93
+ # TODO: Add exception handling for failed futures.
94
+ hash[name] = future.value
95
+ end
96
+ end
97
+ end
98
+
99
+ # Fetch all facts from each node
100
+ #
101
+ # This method calls {#fetch_all} and then blocks until the result
102
+ # is available.
103
+ #
104
+ # @return [Hash{String => Hash}]
105
+ # A hash mapping the node id to a hash containing the resolved facts.
106
+ def to_hash
107
+ fetch_all.value
108
+ end
109
+
110
+ # Flush cached fact values
111
+ #
112
+ # @return [void]
113
+ def flush
114
+ @collections.values.each {|c| c.flush}
115
+ end
116
+ end
@@ -13,6 +13,7 @@ require 'concurrent'
13
13
  # @note This class should be refacter to provide an abstracted interface to
14
14
  # different transport backends like Train, Vagrant, Chloride, etc.
15
15
  #
16
+ # @api public
16
17
  # @since 0.1.0
17
18
  class RFacter::Node
18
19
  extend Forwardable
@@ -34,10 +35,17 @@ class RFacter::Node
34
35
  attr_reader :password
35
36
  # @return [Hash]
36
37
  attr_reader :options
38
+ # @return [String]
39
+ attr_reader :id
37
40
 
38
41
  attr_reader :transport
39
42
 
40
- def initialize(uri, config: RFacter::Config.config, **opts)
43
+ # Returns a new instance of Node
44
+ #
45
+ # @param uri [URI] The URI of the node.
46
+ # @param id [String, nil] An optional string to use when identifying
47
+ # this node.
48
+ def initialize(uri, id: nil, config: RFacter::Config.config, **opts)
41
49
  @config = config
42
50
 
43
51
  @uri = unless uri.is_a?(URI)
@@ -67,6 +75,20 @@ class RFacter::Node
67
75
  @options = @uri.query.nil? ? Hash.new : CGI.parse(@uri.query)
68
76
  @options.update(opts)
69
77
 
78
+ @id = unless id.nil?
79
+ id
80
+ else
81
+ # Create a default from the URI, minus the password and options
82
+ # components.
83
+ id_string = "#{@scheme}://"
84
+ id_string += "#{@user}@" unless @user.nil?
85
+ id_string += @hostname
86
+ id_string += ":#{@port}" unless @port.nil?
87
+ id_string
88
+ end
89
+
90
+ @id.freeze
91
+
70
92
  # TODO: This should be abstracted.
71
93
  @transport = Train.create(@scheme,
72
94
  host: @hostname,
@@ -135,3 +157,39 @@ class RFacter::Node
135
157
  connection.file(path)
136
158
  end
137
159
  end
160
+
161
+
162
+ # Hello dear reader. Hiding at the bottom of this file is a consequence of bad
163
+ # design decisions in Ruby --- specifically the Kernel.autoload feature. Train
164
+ # uses autload to lazily require its subcomponents. However, autoload plays
165
+ # filthy tricks with the resolution of Constants that are not thread-safe. So,
166
+ # this chunk of code is sitting down here out of sight to force these autoloads
167
+ # to be resolved up front so that we can get on with the business of actually
168
+ # saving significant time by using parallelism.
169
+ #
170
+ # See:
171
+ # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/41149
172
+ # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/20238
173
+ #
174
+ # TODO: Remove if this PR lands:
175
+ # https://github.com/chef/train/pull/178
176
+ require 'train/plugins/transport'
177
+ require 'train/plugins/base_connection'
178
+
179
+ require 'train/transports/ssh'
180
+ require 'train/transports/ssh_connection'
181
+ require 'train/transports/winrm'
182
+ require 'train/transports/winrm_connection'
183
+ require 'train/transports/local'
184
+ require 'train/transports/local_file'
185
+ require 'train/transports/local_os'
186
+
187
+ require 'train/extras'
188
+ require 'train/extras/command_wrapper'
189
+ require 'train/extras/file_common'
190
+ require 'train/extras/file_unix'
191
+ require 'train/extras/file_aix'
192
+ require 'train/extras/file_linux'
193
+ require 'train/extras/file_windows'
194
+ require 'train/extras/os_common'
195
+ require 'train/extras/stat'
@@ -1,16 +1,22 @@
1
1
  require 'forwardable'
2
2
 
3
+ require 'concurrent'
4
+
3
5
  require 'rfacter'
4
6
  require_relative '../config'
5
7
  require_relative '../dsl'
6
8
  require_relative 'loader'
7
9
  require_relative 'fact'
8
10
 
9
- # Manage which facts exist and how we access them. Largely just a wrapper
10
- # around a hash of facts.
11
+ # Manage which facts exist on a Node and how we access them.
12
+ #
13
+ # Largely just a wrapper around a hash of facts that have been retrieved from a
14
+ # particular node.
11
15
  #
12
16
  # @api private
17
+ # @since 0.1.0
13
18
  class RFacter::Util::Collection
19
+ include Concurrent::Async
14
20
  # Ensures unqualified namespaces like `Facter` and `Facter::Util` get
15
21
  # re-directed to RFacter shims when the loader calls `instance_eval`
16
22
  include RFacter::DSL
@@ -18,15 +24,21 @@ class RFacter::Util::Collection
18
24
 
19
25
  instance_delegate([:logger] => :@config)
20
26
 
21
- def initialize(config: RFacter::Config.config, **opts)
27
+ # Initialize a new Collection object
28
+ #
29
+ # @param node [RFacter::Node] The node from which this collection
30
+ # should retrieve facts.
31
+ def initialize(node, config: RFacter::Config.config, **opts)
32
+ @node = node
22
33
  @config = config
34
+
23
35
  @facts = Hash.new
24
36
  @internal_loader = RFacter::Util::Loader.new
25
37
  end
26
38
 
27
39
  # Return a fact object by name.
28
- def [](name, node)
29
- value(name, node)
40
+ def [](name)
41
+ value(name)
30
42
  end
31
43
 
32
44
  # Define a new fact or extend an existing fact.
@@ -34,7 +46,7 @@ class RFacter::Util::Collection
34
46
  # @param name [Symbol] The name of the fact to define
35
47
  # @param options [Hash] A hash of options to set on the fact
36
48
  #
37
- # @return [Facter::Util::Fact] The fact that was defined
49
+ # @return [RFacter::Util::Fact] The fact that was defined
38
50
  def define_fact(name, options = {}, &block)
39
51
  fact = create_or_return_fact(name, options)
40
52
 
@@ -53,7 +65,7 @@ class RFacter::Util::Collection
53
65
  # @param name [Symbol] The name of the fact to define
54
66
  # @param options [Hash] A hash of options to set on the fact and resolution
55
67
  #
56
- # @return [Facter::Util::Fact] The fact that was defined
68
+ # @return [RFacter::Util::Fact] The fact that was defined
57
69
  def add(name, options = {}, &block)
58
70
  fact = create_or_return_fact(name, options)
59
71
 
@@ -65,11 +77,11 @@ class RFacter::Util::Collection
65
77
  include Enumerable
66
78
 
67
79
  # Iterate across all of the facts.
68
- def each(node)
80
+ def each
69
81
  load_all
70
82
 
71
- RFacter::DSL::COLLECTION.bind(self) do
72
- RFacter::DSL::NODE.bind(node) do
83
+ COLLECTION.bind(self) do
84
+ NODE.bind(@node) do
73
85
  @facts.each do |name, fact|
74
86
  value = fact.value
75
87
  unless value.nil?
@@ -118,24 +130,22 @@ class RFacter::Util::Collection
118
130
  end
119
131
 
120
132
  # Return a hash of all of our facts.
121
- def to_hash(node)
122
- @facts.inject({}) do |h, ary|
123
- resolved_value = RFacter::DSL::COLLECTION.bind(self) do
124
- RFacter::DSL::NODE.bind(node) do
125
- ary[1].value
133
+ def to_hash
134
+ COLLECTION.bind(self) do
135
+ NODE.bind(@node) do
136
+ @facts.each_with_object({}) do |(name, fact), hash|
137
+ resolved_value = fact.value
138
+
139
+ # For backwards compatibility, convert the fact name to a string.
140
+ hash[name.to_s] = resolved_value unless resolved_value.nil?
126
141
  end
127
142
  end
128
-
129
- # For backwards compatibility, convert the fact name to a string.
130
- h[ary[0].to_s] = resolved_value unless resolved_value.nil?
131
-
132
- h
133
143
  end
134
144
  end
135
145
 
136
- def value(name, node)
137
- RFacter::DSL::COLLECTION.bind(self) do
138
- RFacter::DSL::NODE.bind(node) do
146
+ def value(name)
147
+ COLLECTION.bind(self) do
148
+ NODE.bind(@node) do
139
149
  if fact = fact(name)
140
150
  fact.value
141
151
  end
@@ -153,8 +163,6 @@ class RFacter::Util::Collection
153
163
  if fact.nil?
154
164
  fact = RFacter::Util::Fact.new(name, options)
155
165
  @facts[name] = fact
156
- else
157
- fact.extract_ldapname_option!(options)
158
166
  end
159
167
 
160
168
  fact
@@ -7,6 +7,9 @@ require_relative 'values'
7
7
 
8
8
  # A restricting tag for fact resolution mechanisms. The tag must be true
9
9
  # for the resolution mechanism to be suitable.
10
+ #
11
+ # @api private
12
+ # @since 0.1.0
10
13
  class RFacter::Util::Confine
11
14
  extend Forwardable
12
15
 
@@ -4,11 +4,12 @@ require 'rfacter'
4
4
  require_relative '../config'
5
5
 
6
6
  # This class represents a fact. Each fact has a name and multiple
7
- # {Facter::Util::Resolution resolutions}.
7
+ # {RFacter::Util::Resolution resolutions}.
8
8
  #
9
- # Create facts using {Facter.add}
9
+ # Create facts using {RFacter::DSL::Facter.add Facter.add}
10
10
  #
11
- # @api public
11
+ # @api private
12
+ # @since 0.1.0
12
13
  class RFacter::Util::Fact
13
14
  require_relative '../core/aggregate'
14
15
  require_relative 'resolution'
@@ -21,40 +22,27 @@ class RFacter::Util::Fact
21
22
  # @return [String]
22
23
  attr_accessor :name
23
24
 
24
- # @return [String]
25
- # @deprecated
26
- attr_accessor :ldapname
27
-
28
- # Creates a new fact, with no resolution mechanisms. See {Facter.add}
25
+ # Creates a new fact, with no resolution mechanisms. See {RFacter::DSL::Facter.add}
29
26
  # for the public API for creating facts.
30
27
  # @param name [String] the fact name
31
28
  # @param options [Hash] optional parameters
32
- # @option options [String] :ldapname set the ldapname property on the fact
33
- #
34
- # @api private
35
29
  def initialize(name, config: RFacter::Config.config, **options)
36
30
  @name = name.to_s.downcase.intern
37
31
  @config = config
38
32
 
39
- extract_ldapname_option!(options)
40
-
41
- @ldapname ||= @name.to_s
42
-
43
33
  @resolves = []
44
34
  @searching = false
45
35
 
46
36
  @value = nil
47
37
  end
48
38
 
49
- # Adds a new {Facter::Util::Resolution resolution}. This requires a
39
+ # Adds a new {RFacter::Util::Resolution resolution}. This requires a
50
40
  # block, which will then be evaluated in the context of the new
51
41
  # resolution.
52
42
  #
53
43
  # @param options [Hash] A hash of options to set on the resolution
54
44
  #
55
- # @return [Facter::Util::Resolution]
56
- #
57
- # @api private
45
+ # @return [RFacter::Util::Resolution]
58
46
  def add(options = {}, &block)
59
47
  define_resolution(nil, options, &block)
60
48
  end
@@ -64,9 +52,7 @@ class RFacter::Util::Fact
64
52
  #
65
53
  # @param resolution_name [String] The name of the resolve to define or look up
66
54
  # @param options [Hash] A hash of options to set on the resolution
67
- # @return [Facter::Util::Resolution]
68
- #
69
- # @api public
55
+ # @return [RFacter::Util::Resolution]
70
56
  def define_resolution(resolution_name, options = {}, &block)
71
57
 
72
58
  resolution_type = options.delete(:type) || :simple
@@ -85,7 +71,7 @@ class RFacter::Util::Fact
85
71
  #
86
72
  # @param name [String]
87
73
  #
88
- # @return [Facter::Util::Resolution, nil] The resolution if exists, nil if
74
+ # @return [RFacter::Util::Resolution, nil] The resolution if exists, nil if
89
75
  # it doesn't exist or name is nil
90
76
  def resolution(name)
91
77
  return nil if name.nil?
@@ -96,18 +82,14 @@ class RFacter::Util::Fact
96
82
  # Flushes any cached values.
97
83
  #
98
84
  # @return [void]
99
- #
100
- # @api private
101
85
  def flush
102
86
  @resolves.each { |r| r.flush }
103
87
  @value = nil
104
88
  end
105
89
 
106
90
  # Returns the value for this fact. This searches all resolutions by
107
- # suitability and weight (see {Facter::Util::Resolution}). If no
91
+ # suitability and weight (see {RFacter::Util::Resolution}). If no
108
92
  # suitable resolution is found, it returns nil.
109
- #
110
- # @api public
111
93
  def value
112
94
  return @value if @value
113
95
 
@@ -128,15 +110,6 @@ class RFacter::Util::Fact
128
110
  end
129
111
  end
130
112
 
131
- # @api private
132
- # @deprecated
133
- def extract_ldapname_option!(options)
134
- if options[:ldapname]
135
- logger.warnonce("ldapname is deprecated and will be removed in a future version")
136
- self.ldapname = options.delete(:ldapname)
137
- end
138
- end
139
-
140
113
  private
141
114
 
142
115
  # Are we in the midst of a search?
@@ -8,6 +8,7 @@ require_relative '../dsl'
8
8
  # Load facts on demand.
9
9
  #
10
10
  # @api private
11
+ # @since 0.1.0
11
12
  class RFacter::Util::Loader
12
13
  extend Forwardable
13
14
 
@@ -20,7 +21,6 @@ class RFacter::Util::Loader
20
21
 
21
22
  # Load all resolutions for a single fact.
22
23
  #
23
- # @api public
24
24
  # @param fact [Symbol]
25
25
  def load(fact, collection)
26
26
  # Now load from the search path
@@ -40,8 +40,6 @@ class RFacter::Util::Loader
40
40
  end
41
41
 
42
42
  # Load all facts from all directories.
43
- #
44
- # @api public
45
43
  def load_all(collection)
46
44
  return if defined?(@loaded_all)
47
45
 
@@ -69,7 +67,6 @@ class RFacter::Util::Loader
69
67
  # A warning will be generated for paths that are not
70
68
  # absolute directories.
71
69
  #
72
- # @api public
73
70
  # @return [Array<String>]
74
71
  def search_path
75
72
  search_paths = [File.expand_path('../../facts', __FILE__)]
@@ -11,6 +11,7 @@ require 'rfacter'
11
11
  # - `debugonce`
12
12
  # - `log_exception`
13
13
  #
14
+ # @api private
14
15
  # @since 0.1.0
15
16
  class RFacter::Util::Logger < ::Logger
16
17
  @@warn_messages = Hash.new
@@ -8,6 +8,7 @@ require 'rfacter'
8
8
  # if de-referenced to `nil`. This allows the creation of variables
9
9
  # that must always be bound to a specific value before use.
10
10
  #
11
+ # @api private
11
12
  # @since 0.1.0
12
13
  class RFacter::Util::NonNullable < Concurrent::ThreadLocalVar
13
14
  # @param err_message [String] The error message to raise if
@@ -1,5 +1,9 @@
1
1
  require 'rfacter'
2
2
 
3
+ # Routines for normalizing fact return values
4
+ #
5
+ # @api private
6
+ # @since 0.1.0
3
7
  module RFacter
4
8
  module Util
5
9
  module Normalization
@@ -11,7 +15,6 @@ module RFacter
11
15
 
12
16
  # Recursively normalize the given data structure
13
17
  #
14
- # @api public
15
18
  # @raise [NormalizationError] If the data structure contained an invalid element.
16
19
  # @return [void]
17
20
  def normalize(value)
@@ -33,46 +36,27 @@ module RFacter
33
36
  #
34
37
  # Attempt to normalize and validate the given string.
35
38
  #
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
+ # The string is validate by checking that the string encoding is UTF-8
40
+ # and that the string content matches the encoding. If the string is not
41
+ # an expected encoding then it is converted to UTF-8.
39
42
  #
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
43
  # @raise [NormalizationError] If the string used an unsupported encoding or did not match its encoding
46
44
  # @param value [String]
47
45
  # @return [void]
46
+ def normalize_string(value)
47
+ value = value.encode(Encoding::UTF_8)
48
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
49
+ unless value.valid_encoding?
50
+ raise NormalizationError, "String #{value.inspect} doesn't match the reported encoding #{value.encoding}"
58
51
  end
59
- else
60
- def normalize_string(value)
61
- value = value.encode(Encoding::UTF_8)
62
52
 
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
53
+ value
54
+ rescue EncodingError
55
+ raise NormalizationError, "String encoding #{value.encoding} is not UTF-8 and could not be converted to UTF-8"
71
56
  end
72
57
 
73
58
  # Validate all elements of the array.
74
59
  #
75
- # @api public
76
60
  # @raise [NormalizationError] If one of the elements failed validation
77
61
  # @param value [Array]
78
62
  # @return [void]
@@ -84,7 +68,6 @@ module RFacter
84
68
 
85
69
  # Validate all keys and values of the hash.
86
70
  #
87
- # @api public
88
71
  # @raise [NormalizationError] If one of the keys or values failed normalization
89
72
  # @param value [Hash]
90
73
  # @return [void]
@@ -6,14 +6,15 @@ require_relative '../config'
6
6
  # This represents a fact resolution. A resolution is a concrete
7
7
  # implementation of a fact. A single fact can have many resolutions and
8
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
9
+ # {RFacter::DSL::Facter.add Facter.add} is called, a new resolution is created
10
+ # and added to the set of resolutions for the fact named in the call. Each
11
+ # resolution has a {#has_weight weight}, which defines its priority over other
12
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_.
13
+ # conditions under which it will be chosen. All confinements must be satisfied
14
+ # for a fact to be considered _suitable_.
15
15
  #
16
- # @api public
16
+ # @api private
17
+ # @since 0.1.0
17
18
  class RFacter::Util::Resolution
18
19
  require_relative '../dsl'
19
20
  require_relative '../core/resolvable'
@@ -22,7 +23,6 @@ class RFacter::Util::Resolution
22
23
 
23
24
  instance_delegate([:logger] => :@config)
24
25
 
25
- # @api private
26
26
  attr_accessor :code
27
27
  attr_writer :value
28
28
 
@@ -33,12 +33,10 @@ class RFacter::Util::Resolution
33
33
  # The name of this resolution. The resolution name should be unique with
34
34
  # respect to the given fact.
35
35
  # @return [String]
36
- # @api public
37
36
  attr_accessor :name
38
37
 
39
38
  # @!attribute [r] fact
40
- # @return [Facter::Util::Fact]
41
- # @api private
39
+ # @return [RFacter::Util::Fact]
42
40
  attr_reader :fact
43
41
 
44
42
  def which(command)
@@ -53,8 +51,6 @@ class RFacter::Util::Resolution
53
51
  #
54
52
  # @param name [String] The name of the resolution.
55
53
  # @return [void]
56
- #
57
- # @api private
58
54
  def initialize(name, fact, config: RFacter::Config.config, **options)
59
55
  @name = name
60
56
  @fact = fact
@@ -83,14 +79,7 @@ class RFacter::Util::Resolution
83
79
 
84
80
  instance_eval(&block)
85
81
 
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
82
+ @last_evaluated = block.source_location.join(':')
94
83
  end
95
84
 
96
85
  def set_options(options)
@@ -130,8 +119,6 @@ class RFacter::Util::Resolution
130
119
  # @param [Proc] block The block to determine the resolution's value.
131
120
  # This block is run when the fact is evaluated. Errors raised from
132
121
  # inside the block are rescued and printed to stderr.
133
- #
134
- # @api public
135
122
  def setcode(string = nil, &block)
136
123
  if string
137
124
  @code = Proc.new do
@@ -1,5 +1,9 @@
1
1
  require 'rfacter'
2
2
 
3
+ # Helper methods for coercing items to JSON datatypes
4
+ #
5
+ # @api private
6
+ # @since 0.1.0
3
7
  module RFacter
4
8
  module Util
5
9
  # A util module for facter containing helper methods
@@ -1,3 +1,3 @@
1
1
  module RFacter
2
- VERSION = '0.0.1'.freeze
2
+ VERSION = '0.1.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rfacter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charlie Sharpsteen
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-05-10 00:00:00.000000000 Z
12
+ date: 2017-05-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: train
@@ -40,19 +40,19 @@ dependencies:
40
40
  - !ruby/object:Gem::Version
41
41
  version: '1.0'
42
42
  - !ruby/object:Gem::Dependency
43
- name: inch
43
+ name: rake
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: '0.7'
48
+ version: '12.0'
49
49
  type: :development
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
- version: '0.7'
55
+ version: '12.0'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: rspec
58
58
  requirement: !ruby/object:Gem::Requirement
@@ -67,6 +67,20 @@ dependencies:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
69
  version: '3.1'
70
+ - !ruby/object:Gem::Dependency
71
+ name: yard
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '0.9'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '0.9'
70
84
  description: |
71
85
  RFacter is a library for collecting facts from remote system(s) by executing
72
86
  commands over transports such as SSH and WinRM.
@@ -92,6 +106,7 @@ files:
92
106
  - lib/rfacter/facts/kernelversion.rb
93
107
  - lib/rfacter/facts/networking.rb
94
108
  - lib/rfacter/facts/os.rb
109
+ - lib/rfacter/factset.rb
95
110
  - lib/rfacter/node.rb
96
111
  - lib/rfacter/util/collection.rb
97
112
  - lib/rfacter/util/confine.rb