ridley 0.10.2 → 0.11.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/README.md +147 -216
  2. data/lib/ridley.rb +2 -0
  3. data/lib/ridley/bootstrap_bindings/unix_template_binding.rb +21 -25
  4. data/lib/ridley/bootstrap_bindings/windows_template_binding.rb +29 -34
  5. data/lib/ridley/bootstrapper.rb +2 -2
  6. data/lib/ridley/bootstrapper/context.rb +5 -5
  7. data/lib/ridley/chef.rb +0 -1
  8. data/lib/ridley/chef/cookbook.rb +0 -9
  9. data/lib/ridley/chef_object.rb +128 -0
  10. data/lib/ridley/chef_objects.rb +3 -0
  11. data/lib/ridley/chef_objects/client_object.rb +55 -0
  12. data/lib/ridley/chef_objects/cookbook_object.rb +190 -0
  13. data/lib/ridley/chef_objects/data_bag_item_obect.rb +104 -0
  14. data/lib/ridley/chef_objects/data_bag_object.rb +31 -0
  15. data/lib/ridley/chef_objects/environment_object.rb +59 -0
  16. data/lib/ridley/chef_objects/node_object.rb +161 -0
  17. data/lib/ridley/chef_objects/role_object.rb +62 -0
  18. data/lib/ridley/chef_objects/sandbox_object.rb +58 -0
  19. data/lib/ridley/client.rb +76 -45
  20. data/lib/ridley/connection.rb +1 -1
  21. data/lib/ridley/errors.rb +8 -1
  22. data/lib/ridley/host_connector.rb +26 -6
  23. data/lib/ridley/host_connector/ssh.rb +3 -3
  24. data/lib/ridley/host_connector/ssh/worker.rb +7 -9
  25. data/lib/ridley/host_connector/winrm/worker.rb +4 -5
  26. data/lib/ridley/mixin/bootstrap_binding.rb +1 -12
  27. data/lib/ridley/resource.rb +51 -171
  28. data/lib/ridley/resources/client_resource.rb +18 -68
  29. data/lib/ridley/resources/cookbook_resource.rb +181 -381
  30. data/lib/ridley/resources/data_bag_item_resource.rb +55 -161
  31. data/lib/ridley/resources/data_bag_resource.rb +20 -61
  32. data/lib/ridley/resources/environment_resource.rb +9 -64
  33. data/lib/ridley/resources/node_resource.rb +135 -311
  34. data/lib/ridley/resources/role_resource.rb +1 -57
  35. data/lib/ridley/resources/sandbox_resource.rb +80 -65
  36. data/lib/ridley/resources/search_resource.rb +99 -0
  37. data/lib/ridley/sandbox_uploader.rb +12 -52
  38. data/lib/ridley/version.rb +1 -1
  39. data/spec/acceptance/bootstrapping_spec.rb +1 -1
  40. data/spec/acceptance/client_resource_spec.rb +15 -37
  41. data/spec/acceptance/data_bag_item_resource_spec.rb +8 -14
  42. data/spec/acceptance/data_bag_resource_spec.rb +1 -1
  43. data/spec/acceptance/environment_resource_spec.rb +13 -22
  44. data/spec/acceptance/node_resource_spec.rb +10 -29
  45. data/spec/acceptance/role_resource_spec.rb +14 -13
  46. data/spec/acceptance/sandbox_resource_spec.rb +2 -2
  47. data/spec/support/shared_examples/ridley_resource.rb +2 -23
  48. data/spec/unit/ridley/bootstrap_bindings/unix_template_binding_spec.rb +3 -4
  49. data/spec/unit/ridley/bootstrap_bindings/windows_template_binding_spec.rb +3 -5
  50. data/spec/unit/ridley/bootstrapper/context_spec.rb +2 -3
  51. data/spec/unit/ridley/bootstrapper_spec.rb +1 -1
  52. data/spec/unit/ridley/chef_object_spec.rb +240 -0
  53. data/spec/unit/ridley/chef_objects/client_object_spec.rb +11 -0
  54. data/spec/unit/ridley/chef_objects/cookbook_object_spec.rb +93 -0
  55. data/spec/unit/ridley/chef_objects/data_bag_item_object_spec.rb +74 -0
  56. data/spec/unit/ridley/chef_objects/data_bag_object_spec.rb +9 -0
  57. data/spec/unit/ridley/chef_objects/environment_object_spec.rb +57 -0
  58. data/spec/unit/ridley/chef_objects/node_object_spec.rb +252 -0
  59. data/spec/unit/ridley/chef_objects/role_object_spec.rb +57 -0
  60. data/spec/unit/ridley/chef_objects/sandbox_object_spec.rb +66 -0
  61. data/spec/unit/ridley/client_spec.rb +51 -51
  62. data/spec/unit/ridley/host_connector/ssh/worker_spec.rb +4 -4
  63. data/spec/unit/ridley/host_connector/ssh_spec.rb +26 -24
  64. data/spec/unit/ridley/host_connector/winrm/worker_spec.rb +3 -4
  65. data/spec/unit/ridley/host_connector/winrm_spec.rb +4 -4
  66. data/spec/unit/ridley/host_connector_spec.rb +40 -3
  67. data/spec/unit/ridley/mixin/bootstrap_binding_spec.rb +1 -1
  68. data/spec/unit/ridley/resource_spec.rb +81 -109
  69. data/spec/unit/ridley/resources/client_resource_spec.rb +18 -33
  70. data/spec/unit/ridley/resources/cookbook_resource_spec.rb +56 -230
  71. data/spec/unit/ridley/resources/data_bag_item_resource_spec.rb +2 -57
  72. data/spec/unit/ridley/resources/data_bag_resource_spec.rb +12 -7
  73. data/spec/unit/ridley/resources/environment_resource_spec.rb +10 -118
  74. data/spec/unit/ridley/resources/node_resource_spec.rb +83 -394
  75. data/spec/unit/ridley/resources/role_resource_spec.rb +2 -56
  76. data/spec/unit/ridley/resources/sandbox_resource_spec.rb +139 -136
  77. data/spec/unit/ridley/resources/search_resource_spec.rb +234 -0
  78. data/spec/unit/ridley/sandbox_uploader_spec.rb +13 -58
  79. metadata +36 -17
  80. data/lib/ridley/chef/chefignore.rb +0 -76
  81. data/lib/ridley/resources/encrypted_data_bag_item_resource.rb +0 -55
  82. data/lib/ridley/resources/search.rb +0 -101
  83. data/spec/fixtures/chefignore +0 -8
  84. data/spec/unit/ridley/chef/chefignore_spec.rb +0 -40
  85. data/spec/unit/ridley/resources/search_spec.rb +0 -221
@@ -0,0 +1,62 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <reset@riotgames.com>
3
+ class RoleObject < Ridley::ChefObject
4
+ set_chef_id "name"
5
+ set_chef_type "role"
6
+ set_chef_json_class "Chef::Role"
7
+
8
+ attribute :name,
9
+ required: true
10
+
11
+ attribute :description,
12
+ default: String.new
13
+
14
+ attribute :default_attributes,
15
+ default: Hashie::Mash.new
16
+
17
+ attribute :override_attributes,
18
+ default: Hashie::Mash.new
19
+
20
+ attribute :run_list,
21
+ default: Array.new
22
+
23
+ attribute :env_run_lists,
24
+ default: Hash.new
25
+
26
+ # Set a role level override attribute given the dotted path representation of the Chef
27
+ # attribute and value
28
+ #
29
+ # @example setting and saving a node level override attribute
30
+ #
31
+ # obj = node.role("why_god_why")
32
+ # obj.set_override_attribute("my_app.billing.enabled", false)
33
+ # obj.save
34
+ #
35
+ # @param [String] key
36
+ # @param [Object] value
37
+ #
38
+ # @return [HashWithIndifferentAccess]
39
+ def set_override_attribute(key, value)
40
+ attr_hash = HashWithIndifferentAccess.from_dotted_path(key, value)
41
+ self.override_attributes = self.override_attributes.deep_merge(attr_hash)
42
+ end
43
+
44
+ # Set a role level default attribute given the dotted path representation of the Chef
45
+ # attribute and value
46
+ #
47
+ # @example setting and saving a node level default attribute
48
+ #
49
+ # obj = node.role("why_god_why")
50
+ # obj.set_default_attribute("my_app.billing.enabled", false)
51
+ # obj.save
52
+ #
53
+ # @param [String] key
54
+ # @param [Object] value
55
+ #
56
+ # @return [HashWithIndifferentAccess]
57
+ def set_default_attribute(key, value)
58
+ attr_hash = HashWithIndifferentAccess.from_dotted_path(key, value)
59
+ self.default_attributes = self.default_attributes.deep_merge(attr_hash)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,58 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <reset@riotgames.com>
3
+ class SandboxObject < ChefObject
4
+ set_chef_id "sandbox_id"
5
+
6
+ attribute :sandbox_id,
7
+ type: String
8
+
9
+ attribute :uri,
10
+ type: String
11
+
12
+ attribute :checksums,
13
+ type: Hash
14
+
15
+ attribute :is_completed,
16
+ type: Boolean,
17
+ default: false
18
+
19
+ # Return information about the given checksum
20
+ #
21
+ # @example
22
+ # sandbox.checksum("e5a0f6b48d0712382295ff30bec1f9cc") => {
23
+ # needs_upload: true,
24
+ # url: "https://s3.amazonaws.com/opscode-platform-production-data/organization"
25
+ # }
26
+ #
27
+ # @param [#to_sym] chk_id
28
+ # checksum to retrieve information about
29
+ #
30
+ # @return [Hash]
31
+ # a hash containing the checksum information
32
+ def checksum(chk_id)
33
+ checksums[chk_id.to_sym]
34
+ end
35
+
36
+ # Concurrently upload all of this sandboxes files into the checksum containers of the sandbox
37
+ #
38
+ # @param [Hash] checksums
39
+ # a hash of file checksums and file paths
40
+ #
41
+ # @example
42
+ # sandbox.upload(
43
+ # "e5a0f6b48d0712382295ff30bec1f9cc" => "/Users/reset/code/rbenv-cookbook/recipes/default.rb",
44
+ # "de6532a7fbe717d52020dc9f3ae47dbe" => "/Users/reset/code/rbenv-cookbook/recipes/ohai_plugin.rb"
45
+ # )
46
+ def upload(checksums)
47
+ resource.upload(self, checksums)
48
+ end
49
+
50
+ # Notify the Chef Server that uploading to this sandbox has completed
51
+ #
52
+ # @raise [Ridley::Errors::SandboxCommitError]
53
+ def commit
54
+ response = resource.commit(self)
55
+ set_attribute(:is_completed, response[:is_completed])
56
+ end
57
+ end
58
+ end
data/lib/ridley/client.rb CHANGED
@@ -22,13 +22,40 @@ module Ridley
22
22
  # r.save
23
23
  #
24
24
  # connection.role.find("new-role") => <#Ridley::RoleResource: @name="new-role">
25
- class Client < Celluloid::SupervisionGroup
25
+ class Client
26
+ class ConnectionSupervisor < ::Celluloid::SupervisionGroup
27
+ def initialize(registry, options)
28
+ super(registry)
29
+ pool(Ridley::Connection, size: options[:pool_size], args: [
30
+ options[:server_url],
31
+ options[:client_name],
32
+ options[:client_key],
33
+ options.slice(*Ridley::Connection::VALID_OPTIONS)
34
+ ], as: :connection_pool)
35
+ end
36
+ end
37
+
38
+ class ResourcesSupervisor < ::Celluloid::SupervisionGroup
39
+ def initialize(registry, connection_registry, options)
40
+ super(registry)
41
+ supervise_as :client_resource, Ridley::ClientResource, connection_registry
42
+ supervise_as :cookbook_resource, Ridley::CookbookResource, connection_registry,
43
+ options[:client_name], options[:client_key], options.slice(*Ridley::Connection::VALID_OPTIONS)
44
+ supervise_as :data_bag_resource, Ridley::DataBagResource, connection_registry,
45
+ options[:encrypted_data_bag_secret]
46
+ supervise_as :environment_resource, Ridley::EnvironmentResource, connection_registry
47
+ supervise_as :node_resource, Ridley::NodeResource, connection_registry, options
48
+ supervise_as :role_resource, Ridley::RoleResource, connection_registry
49
+ supervise_as :sandbox_resource, Ridley::SandboxResource, connection_registry,
50
+ options[:client_name], options[:client_key], options.slice(*Ridley::Connection::VALID_OPTIONS)
51
+ supervise_as :search_resource, Ridley::SearchResource, connection_registry
52
+ end
53
+ end
54
+
26
55
  class << self
27
56
  def open(options = {}, &block)
28
57
  cli = new(options)
29
58
  cli.evaluate(&block)
30
- ensure
31
- cli.terminate if cli && cli.alive?
32
59
  end
33
60
 
34
61
  # @raise [ArgumentError]
@@ -37,13 +64,13 @@ module Ridley
37
64
  def validate_options(options)
38
65
  missing = (REQUIRED_OPTIONS - options.keys)
39
66
 
40
- unless missing.empty?
67
+ if missing.any?
41
68
  missing.collect! { |opt| "'#{opt}'" }
42
69
  raise ArgumentError, "Missing required option(s): #{missing.join(', ')}"
43
70
  end
44
71
 
45
72
  missing_values = options.slice(*REQUIRED_OPTIONS).select { |key, value| !value.present? }
46
- unless missing_values.empty?
73
+ if missing_values.any?
47
74
  values = missing_values.keys.collect { |opt| "'#{opt}'" }
48
75
  raise ArgumentError, "Missing value for required option(s): '#{values.join(', ')}'"
49
76
  end
@@ -57,8 +84,14 @@ module Ridley
57
84
  ].freeze
58
85
 
59
86
  extend Forwardable
87
+ include Celluloid
60
88
  include Ridley::Logging
61
89
 
90
+ finalizer do
91
+ @connection_supervisor.terminate if @connection_supervisor && @connection_supervisor.alive?
92
+ @resources_supervisor.terminate if @resources_supervisor && @resources_supervisor.alive?
93
+ end
94
+
62
95
  def_delegator :connection, :build_url
63
96
  def_delegator :connection, :scheme
64
97
  def_delegator :connection, :host
@@ -66,6 +99,7 @@ module Ridley
66
99
  def_delegator :connection, :path_prefix
67
100
  def_delegator :connection, :url_prefix
68
101
 
102
+ def_delegator :connection, :organization
69
103
  def_delegator :connection, :client_key
70
104
  def_delegator :connection, :client_key=
71
105
  def_delegator :connection, :client_name
@@ -111,16 +145,16 @@ module Ridley
111
145
  # * :verify (Boolean) [true] set to false to disable SSL verification
112
146
  # @option options [URI, String, Hash] :proxy
113
147
  # URI, String, or Hash of HTTP proxy options
148
+ # @option options [Integer] :pool_size (4)
149
+ # size of the connection pool
114
150
  #
115
151
  # @raise [Errors::ClientKeyFileNotFound] if the option for :client_key does not contain
116
152
  # a file path pointing to a readable client key
117
153
  def initialize(options = {})
118
- log.info { "Ridley starting..." }
119
- super()
120
-
121
154
  @options = options.reverse_merge(
122
155
  ssh: Hash.new,
123
- winrm: Hash.new
156
+ winrm: Hash.new,
157
+ pool_size: 4
124
158
  ).deep_symbolize_keys
125
159
  self.class.validate_options(@options)
126
160
 
@@ -139,79 +173,80 @@ module Ridley
139
173
  @encrypted_data_bag_secret_path = File.expand_path(@options[:encrypted_data_bag_secret_path])
140
174
  end
141
175
 
176
+ @options[:encrypted_data_bag_secret] = encrypted_data_bag_secret
177
+
142
178
  unless @options[:client_key].present? && File.exist?(@options[:client_key])
143
179
  raise Errors::ClientKeyFileNotFound, "client key not found at: '#{@options[:client_key]}'"
144
180
  end
145
181
 
146
- pool(Ridley::Connection, size: 4, args: [
147
- @options[:server_url],
148
- @options[:client_name],
149
- @options[:client_key],
150
- @options.slice(*Connection::VALID_OPTIONS)
151
- ], as: :connection_pool)
182
+ @connection_registry = Celluloid::Registry.new
183
+ @resources_registry = Celluloid::Registry.new
184
+ @connection_supervisor = ConnectionSupervisor.new(@connection_registry, @options)
185
+ @resources_supervisor = ResourcesSupervisor.new(@resources_registry, @connection_registry, @options)
152
186
  end
153
187
 
154
- # @return [Ridley::ChainLink]
188
+ # @return [Ridley::ClientResource]
155
189
  def client
156
- ChainLink.new(Actor.current, Ridley::ClientResource)
190
+ @resources_registry[:client_resource]
157
191
  end
158
192
 
159
- # @return [Ridley::ChainLink]
193
+ # @return [Ridley::CookbookResource]
160
194
  def cookbook
161
- ChainLink.new(Actor.current, Ridley::CookbookResource)
195
+ @resources_registry[:cookbook_resource]
162
196
  end
163
197
 
164
- # @return [Ridley::ChainLink]
198
+ # @return [Ridley::DataBagResource]
165
199
  def data_bag
166
- ChainLink.new(Actor.current, Ridley::DataBagResource)
200
+ @resources_registry[:data_bag_resource]
167
201
  end
168
202
 
169
- # @return [Ridley::ChainLink]
203
+ # @return [Ridley::EnvironmentResource]
170
204
  def environment
171
- ChainLink.new(Actor.current, Ridley::EnvironmentResource)
205
+ @resources_registry[:environment_resource]
172
206
  end
173
207
 
174
- # @return [Ridley::ChainLink]
208
+ # @return [Ridley::NodeResource]
175
209
  def node
176
- ChainLink.new(Actor.current, Ridley::NodeResource)
210
+ @resources_registry[:node_resource]
177
211
  end
178
212
 
179
- # @return [Ridley::ChainLink]
213
+ # @return [Ridley::RoleResource]
180
214
  def role
181
- ChainLink.new(Actor.current, Ridley::RoleResource)
215
+ @resources_registry[:role_resource]
182
216
  end
183
217
 
184
- # @return [Ridley::ChainLink]
218
+ # @return [Ridley::SandboxResource]
185
219
  def sandbox
186
- ChainLink.new(Actor.current, Ridley::SandboxResource)
220
+ @resources_registry[:sandbox_resource]
187
221
  end
188
222
 
189
- # Creates an runs a new Ridley::Search
190
- #
191
- # @see Ridley::Search#run
223
+ # Perform a search the Chef Server
192
224
  #
193
- # @param [String, Symbol] index
194
- # @param [String, nil] query
225
+ # @param [#to_sym, #to_s] index
226
+ # @param [#to_s] query_string
195
227
  #
196
228
  # @option options [String] :sort
229
+ # a sort string such as 'name DESC'
197
230
  # @option options [Integer] :rows
231
+ # how many rows to return
198
232
  # @option options [Integer] :start
233
+ # the result number to start from
199
234
  #
200
235
  # @return [Hash]
201
236
  def search(index, query = nil, options = {})
202
- Ridley::Search.new(Actor.current, index, query, options).run
237
+ @resources_registry[:search_resource].run(index, query, options)
203
238
  end
204
239
 
205
240
  # Return the array of all possible search indexes for the including connection
206
241
  #
207
242
  # @example
208
243
  # conn = Ridley.new(...)
209
- # conn.search_indexes =>
244
+ # conn.search_indexes =>
210
245
  # [:client, :environment, :node, :role, :"ridley-two", :"ridley-one"]
211
246
  #
212
247
  # @return [Array<Symbol, String>]
213
248
  def search_indexes
214
- Ridley::Search.indexes(Actor.current)
249
+ @resources_registry[:search_resource].indexes
215
250
  end
216
251
 
217
252
  # The encrypted data bag secret for this connection.
@@ -241,16 +276,12 @@ module Ridley
241
276
  end
242
277
  alias_method :sync, :evaluate
243
278
 
244
- def finalize
245
- connection.terminate if connection && connection.alive?
246
- end
247
-
248
- def connection
249
- @registry[:connection_pool]
250
- end
251
-
252
279
  private
253
280
 
281
+ def connection
282
+ @connection_registry[:connection_pool]
283
+ end
284
+
254
285
  def method_missing(method, *args, &block)
255
286
  if block_given?
256
287
  @self_before_instance_eval ||= eval("self", block.binding)
@@ -96,7 +96,7 @@ module Ridley
96
96
  # we expect. Caught exceptions are re-raised with Celluloid#abort so we don't crash the connection.
97
97
  def run_request(*args)
98
98
  super
99
- rescue Errors::HTTPError => ex
99
+ rescue Errors::HTTPError, Faraday::Error => ex
100
100
  abort(ex)
101
101
  end
102
102
 
data/lib/ridley/errors.rb CHANGED
@@ -34,6 +34,11 @@ module Ridley
34
34
  end
35
35
 
36
36
  class CookbookSyntaxError < RidleyError; end
37
+ class EncryptedDataBagSecretNotSet < RidleyError
38
+ def message
39
+ "no encrypted data bag secret was set for this Ridley connection"
40
+ end
41
+ end
37
42
 
38
43
  class BootstrapError < RidleyError; end
39
44
  class ClientKeyFileNotFound < BootstrapError; end
@@ -54,6 +59,8 @@ module Ridley
54
59
  end
55
60
 
56
61
  class FrozenCookbook < RidleyError; end
62
+ class SandboxCommitError < RidleyError; end
63
+ class PermissionDenied < RidleyError; end
57
64
 
58
65
  class HTTPError < RidleyError
59
66
  class << self
@@ -74,7 +81,7 @@ module Ridley
74
81
 
75
82
  def error_map
76
83
  @@error_map ||= Hash.new
77
- end
84
+ end
78
85
  end
79
86
 
80
87
  attr_reader :env
@@ -13,13 +13,35 @@ module Ridley
13
13
  DEFAULT_WINRM_PORT = 5985.freeze
14
14
 
15
15
  class << self
16
+ # Create a new connection worker for the given host. An SSH or WinRM connection will be returned
17
+ # depending on which ports are open on the target host.
18
+ #
19
+ # @param [String] host
20
+ # host to create a connector for
21
+ #
22
+ # @option options [Hash] ssh
23
+ # * :user (String) a shell user that will login to each node and perform the bootstrap command on
24
+ # * :password (String) the password for the shell user that will perform the bootstrap
25
+ # * :keys (Array, String) an array of keys (or a single key) to authenticate the ssh user with instead of a password
26
+ # * :timeout (Float) [5.0] timeout value for SSH bootstrap
27
+ # @option options [Hash] :winrm
28
+ # * :user (String) a user that will login to each node and perform the bootstrap command on
29
+ # * :password (String) the password for the user that will perform the bootstrap
30
+ # * :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on
31
+ #
32
+ # @return [SSH::Worker, WinRM::Worker]
33
+ def new(host, options = {})
34
+ HostConnector.best_connector_for(host, options) do |host_connector|
35
+ host_connector::Worker.new(host, options)
36
+ end
37
+ end
38
+
16
39
  # Finds and returns the best HostConnector for a given host
17
40
  #
18
41
  # @param host [String]
19
42
  # the host to attempt to connect to
20
43
  # @option options [Hash] :ssh
21
44
  # * :port (Fixnum) the ssh port to connect on the node the bootstrap will be performed on (22)
22
- # * :timeout (Float) [5.0] timeout value for testing SSH connection
23
45
  # @option options [Hash] :winrm
24
46
  # * :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)
25
47
  # @param block [Proc]
@@ -28,7 +50,7 @@ module Ridley
28
50
  # @return [Ridley::HostConnector] a class under Ridley::HostConnector
29
51
  def best_connector_for(host, options = {}, &block)
30
52
  ssh_port, winrm_port = parse_port_options(options)
31
- if connector_port_open?(host, ssh_port, options[:ssh][:timeout])
53
+ if connector_port_open?(host, ssh_port)
32
54
  host_connector = Ridley::HostConnector::SSH
33
55
  elsif connector_port_open?(host, winrm_port)
34
56
  host_connector = Ridley::HostConnector::WinRM
@@ -50,12 +72,10 @@ module Ridley
50
72
  # the host to attempt to connect to
51
73
  # @param port [Fixnum]
52
74
  # the port to attempt to connect on
53
- # @param timeout [Float]
54
- # the number of seconds to wait (default: 3)
55
75
  #
56
76
  # @return [Boolean]
57
- def connector_port_open?(host, port, timeout=3)
58
- Timeout::timeout(timeout) do
77
+ def connector_port_open?(host, port)
78
+ Timeout::timeout(3) do
59
79
  socket = TCPSocket.new(host, port)
60
80
  socket.close
61
81
  end