rfacter 0.0.1 → 0.1.0

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