etcd-discovery 1.0.6 → 1.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
  SHA256:
3
- metadata.gz: 17118bb040e02a6a81848eb96bc6eab9490a2ed3c3f1c9718073580add409ebd
4
- data.tar.gz: 8524c1cd5eee0558e12abadaa8478e95c418c92878328aa890d2ab2aad6a2781
3
+ metadata.gz: 3453532202e1683980506e000b3e4b6ec564f4d1da1aba5fe43e4ad6976b1658
4
+ data.tar.gz: 4cdbc6f516d3419476f5fab91a42ceb0b8611c925edfad3843a7af8e3a48b89e
5
5
  SHA512:
6
- metadata.gz: b1eb7a2fda116458395f0502ffcc44fd1e65083321b4eff7dc10ca3413b11d5ec5d0bac13df8b213228bc37482f09980f21267a70b04d871c1639c35b71f3fca
7
- data.tar.gz: cfd3a9cae7bf06ebe471e8269f8f82f925d0aa59bd07cebd935e8b48bdabc0f7effa961cecb206c1509415557882d0546aac4a089f775fa596dba6aee5ea6616
6
+ metadata.gz: 9474e53f69a98deb4b570c0f89c1811cb363bb2630430a02823833b61d72aafd01b084e5b7165b703483dd1a462d3eb365c7ec30ffec9745e741104c989dd3a1
7
+ data.tar.gz: 11427a5f9737d2110d3e82dc5febcc33e5573f6f4bd7f0bc89367b4e450c43a668abcda881dea6435c8a1422102925b7107ceeb8dd0f841a1eea5c168ca8435b
@@ -17,11 +17,11 @@ module EtcdDiscovery
17
17
 
18
18
  def validate
19
19
  if use_ssl
20
- if cacert.nil? or !File.exists? cacert
20
+ if cacert.nil? || !File.exist?(cacert)
21
21
  raise InvalidSSLConfig, "cacert"
22
- elsif ssl_key.nil? or !File.exists? ssl_key
22
+ elsif ssl_key.nil? || !File.exist?(ssl_key)
23
23
  raise InvalidSSLConfig, "ssl_key"
24
- elsif ssl_cert.nil? or !File.exists? ssl_cert
24
+ elsif ssl_cert.nil? || !File.exist?(ssl_cert)
25
25
  raise InvalidSSLConfig, "ssl_cert"
26
26
  end
27
27
  end
@@ -13,11 +13,11 @@ module EtcdDiscovery
13
13
  else
14
14
  raise TypeError, "requires a Etcd::Node or a Hash, not a #{arg.class}"
15
15
  end
16
- if !attributes.has_key?('name') or !attributes.has_key?('ports')
16
+ if !attributes.has_key?("name") || !attributes.has_key?("ports")
17
17
  raise InvalidHost, "attributes 'name' and 'ports' should be defined"
18
18
  end
19
- attributes['user'] = "" if attributes['user'].nil?
20
- attributes['password'] = "" if attributes['password'].nil?
19
+ attributes["user"] = "" if attributes["user"].nil?
20
+ attributes["password"] = "" if attributes["password"].nil?
21
21
  end
22
22
 
23
23
  def to_json
@@ -27,40 +27,40 @@ module EtcdDiscovery
27
27
  def to_uri(schemes = ["https", "http"])
28
28
  a = attributes # Shorten name
29
29
  schemes = [schemes] if !schemes.is_a?(Array)
30
- scheme = schemes.select{|s|
31
- !a['ports'][s].nil?
32
- }.first
33
- if a['user'].empty?
34
- URI("#{scheme}://#{a['name']}:#{a['ports'][scheme]}")
30
+ scheme = schemes.find { |s|
31
+ !a["ports"][s].nil?
32
+ }
33
+ if a["user"].empty?
34
+ URI("#{scheme}://#{a["name"]}:#{a["ports"][scheme]}")
35
35
  else
36
- URI("#{scheme}://#{a['user']}:#{a['password']}@#{a['name']}:#{a['ports'][scheme]}")
36
+ URI("#{scheme}://#{a["user"]}:#{a["password"]}@#{a["name"]}:#{a["ports"][scheme]}")
37
37
  end
38
38
  end
39
39
 
40
40
  def to_private_uri(schemes = ["https", "http"])
41
41
  a = attributes
42
- if a['private_hostname'].empty?
43
- return self.to_uri(schemes)
42
+ if a["private_hostname"].empty?
43
+ return to_uri(schemes)
44
44
  end
45
45
  schemes = [schemes] if !schemes.is_a?(Array)
46
- scheme = schemes.select{ |s|
47
- !a['private_ports'][s].nil?
48
- }.first
46
+ scheme = schemes.find { |s|
47
+ !a["private_ports"][s].nil?
48
+ }
49
49
 
50
- if a['user'].nil? || a['user'] == ""
51
- URI("#{scheme}://#{a['private_hostname']}:#{a['private_ports'][scheme]}")
50
+ if a["user"].nil? || a["user"] == ""
51
+ URI("#{scheme}://#{a["private_hostname"]}:#{a["private_ports"][scheme]}")
52
52
  else
53
- URI("#{scheme}://#{a['user']}:#{a['password']}@#{a['private_hostname']}:#{a['private_ports'][scheme]}")
53
+ URI("#{scheme}://#{a["user"]}:#{a["password"]}@#{a["private_hostname"]}:#{a["private_ports"][scheme]}")
54
54
  end
55
55
  end
56
56
 
57
57
  def set_credentials(user, password)
58
- @attributes['user'] = user
59
- @attributes['password'] = password
58
+ @attributes["user"] = user
59
+ @attributes["password"] = password
60
60
  end
61
61
 
62
62
  def to_s
63
- self.to_uri.to_s
63
+ to_uri.to_s
64
64
  end
65
65
  end
66
66
  end
@@ -1,4 +1,4 @@
1
- require 'securerandom'
1
+ require "securerandom"
2
2
 
3
3
  module EtcdDiscovery
4
4
  class InvalidStateError < StandardError
@@ -25,17 +25,27 @@ module EtcdDiscovery
25
25
  @logger = Logger.new($stdout)
26
26
 
27
27
  if host.is_a? Hash
28
- @host = Host.new host.merge('service_name' => service, 'uuid' => SecureRandom.uuid)
28
+ if host.has_key?("uuid")
29
+ host_uuid = "#{SecureRandom.uuid}-#{host["private_hostname"]}"
30
+ host = host.merge("uuid" => host_uuid)
31
+ end
32
+ @host = Host.new host
29
33
  elsif host.is_a? EtcdDiscovery::Host
34
+ if host.attributes.has_key?("uuid")
35
+ host.attributes["uuid"] = "#{SecureRandom.uuid}-#{host.attributes["private_hostname"]}"
36
+ end
30
37
  @host = host
31
38
  else
32
39
  raise TypeError, "host should be a Hash or a Etcd::Host, is a #{host.class}"
33
40
  end
34
41
 
42
+ # This attribute is later used when instantiating EtcdDiscovery::Service. We want this value to be the content of `service`, always.
43
+ @host.attributes["service_name"] = service
44
+
35
45
  @service = EtcdDiscovery::Service.new service_params
36
46
  @state = :new
37
- @user = @host.attributes['user']
38
- @password = @host.attributes['password']
47
+ @user = @host.attributes["user"]
48
+ @password = @host.attributes["password"]
39
49
  end
40
50
 
41
51
  def register
@@ -49,29 +59,31 @@ module EtcdDiscovery
49
59
  service_value = @service.to_json
50
60
 
51
61
  # Do not start credentials synchro if the service is not public or has no credentials
52
- if @service.attributes['public'] && (@service.attributes['user'].present? || @service.attributes['password'].present?)
53
- @watcher = Thread.new {
54
- @logger.warn "Watcher #{@service.attributes['name']} started"
62
+ if @service.attributes["public"] && (@service.attributes["user"].present? || @service.attributes["password"].present?)
63
+ @watcher = Thread.new do
64
+ @logger.warn "Watcher #{@service.attributes["name"]} started"
55
65
  index = 0
56
66
  while @state == :started
57
67
  begin
58
- resp = client.watch service_key, { index: index }
68
+ resp = client.watch service_key, index: index
59
69
  rescue => e
60
70
  @logger.warn "Fail to watch #{service_key}: #{e}, #{e.message}, #{e.class}"
71
+ index = 0
72
+ sleep(config.register_ttl / 2)
61
73
  next
62
74
  end
63
75
  value = JSON.parse resp.node.value
64
- @user = value['user']
65
- @password = value['password']
76
+ @user = value["user"]
77
+ @password = value["password"]
66
78
  @host.set_credentials user, password
67
79
  @service.set_credentials user, password
68
80
  index = resp.etcd_index
69
81
  end
70
- }
82
+ end
71
83
  end
72
84
 
73
85
  client.set(service_key, value: service_value)
74
- @thread = Thread.new {
86
+ @thread = Thread.new do
75
87
  @logger.warn "Register '#{@service}' started"
76
88
  while @state == :started
77
89
  value = @host.to_json
@@ -83,18 +95,17 @@ module EtcdDiscovery
83
95
  sleep config.register_renew
84
96
  end
85
97
  @logger.warn "Register '#{@service}' stopped"
86
- }
87
-
98
+ end
88
99
 
89
- return self
100
+ self
90
101
  end
91
102
 
92
103
  def stop
93
104
  raise InvalidStateError.new(@state, :started) if @state != :started
94
105
  @logger.debug "Set state to :stopped"
95
106
  @state = :stopped
96
- @logger.debug "Delete #{key}"
97
- client.delete(key)
107
+ @logger.debug "Delete #{host_key}"
108
+ client.delete(host_key)
98
109
  end
99
110
 
100
111
  def client
@@ -106,24 +117,24 @@ module EtcdDiscovery
106
117
  end
107
118
 
108
119
  def host_key
109
- "/services/#{@service.attributes['name']}/#{@host.attributes['uuid']}"
120
+ "/services/#{@service.attributes["name"]}/#{@host.attributes["uuid"]}"
110
121
  end
111
122
 
112
123
  def service_key
113
- "/services_infos/#{@service.attributes['name']}"
124
+ "/services_infos/#{@service.attributes["name"]}"
114
125
  end
115
126
 
116
127
  def service_params
117
128
  params = {
118
- 'name' => host.attributes['service_name'],
119
- 'critical' => host.attributes['critical'],
120
- 'user' => host.attributes['user'],
121
- 'password' => host.attributes['password'],
122
- 'public' => host.attributes['public']
129
+ "name" => @host.attributes["service_name"],
130
+ "critical" => @host.attributes["critical"],
131
+ "user" => @host.attributes["user"],
132
+ "password" => @host.attributes["password"],
133
+ "public" => @host.attributes["public"]
123
134
  }
124
- params['hostname'] = host.attributes['name']if params['public']
125
- params['ports'] = host.attributes['ports'] if params['public']
126
- return params
135
+ params["hostname"] = @host.attributes["name"] if params["public"]
136
+ params["ports"] = @host.attributes["ports"] if params["public"]
137
+ params
127
138
  end
128
139
  end
129
140
  end
@@ -21,39 +21,39 @@ module EtcdDiscovery
21
21
  client = EtcdDiscovery.config.client
22
22
  begin
23
23
  service = client.get("/services_infos/#{service}")
24
- return new service.node
24
+ new service.node
25
25
  rescue Etcd::KeyNotFound
26
- return new('name' => service)
26
+ new("name" => service)
27
27
  end
28
28
  end
29
29
 
30
30
  def all
31
31
  client = EtcdDiscovery.config.client
32
32
  begin
33
- node = client.get("/services/#{attributes['name']}", recursive: true).node
33
+ node = client.get("/services/#{attributes["name"]}", recursive: true).node
34
34
  rescue Etcd::KeyNotFound
35
- raise ServiceNotFound, attributes['name']
35
+ raise ServiceNotFound, attributes["name"]
36
36
  end
37
- raise ServiceNotFound, attributes['name'] if node.children.empty?
38
- return node.children.map { |c| Host.new(c) }
37
+ raise ServiceNotFound, attributes["name"] if node.children.empty?
38
+ node.children.map { |c| Host.new(c) }
39
39
  end
40
40
 
41
41
  def one
42
42
  all.sample
43
43
  end
44
44
 
45
- def to_uri(schemes = ['https', 'http'])
45
+ def to_uri(schemes = ["https", "http"])
46
46
  a = attributes
47
- return one.to_uri(schemes) unless a['public']
47
+ return one.to_uri(schemes) unless a["public"]
48
48
 
49
49
  schemes = [schemes] if !schemes.is_a?(Array)
50
50
  scheme = schemes.reject do |s|
51
- a['ports'][s].nil?
51
+ a["ports"][s].nil?
52
52
  end.first
53
- if a['user'].nil? || a['user'] == ''
54
- URI("#{scheme}://#{a['hostname']}:#{a['ports'][scheme]}")
53
+ if a["user"].nil? || a["user"] == ""
54
+ URI("#{scheme}://#{a["hostname"]}:#{a["ports"][scheme]}")
55
55
  else
56
- URI("#{scheme}://#{a['user']}:#{a['password']}@#{a['hostname']}:#{a['ports'][scheme]}")
56
+ URI("#{scheme}://#{a["user"]}:#{a["password"]}@#{a["hostname"]}:#{a["ports"][scheme]}")
57
57
  end
58
58
  end
59
59
 
@@ -66,8 +66,8 @@ module EtcdDiscovery
66
66
  end
67
67
 
68
68
  def set_credentials(user, password)
69
- @attributes['user'] = user
70
- @attributes['password'] = password
69
+ @attributes["user"] = user
70
+ @attributes["password"] = password
71
71
  end
72
72
  end
73
73
  end
@@ -1,11 +1,11 @@
1
1
  ## Require files from etcd-discovery
2
2
 
3
- require 'etcd'
4
- require 'json'
5
- require 'logger'
3
+ require "etcd"
4
+ require "json"
5
+ require "logger"
6
6
 
7
7
  dir = File.join File.dirname(__FILE__), "etcd-discovery"
8
- Dir["#{dir}/*.rb"].each do |file|
8
+ Dir["#{dir}/*.rb"].sort.each do |file|
9
9
  require file
10
10
  end
11
11
 
@@ -17,7 +17,7 @@ module EtcdDiscovery
17
17
  end
18
18
 
19
19
  def self.configure(&block)
20
- yield config if block_given?
20
+ yield config if block
21
21
  config.validate
22
22
  end
23
23
 
@@ -1,16 +1,16 @@
1
- require 'spec_helper.rb'
1
+ require "spec_helper"
2
2
 
3
3
  RSpec.describe EtcdDiscovery::Registrar do
4
4
  context "without a running client" do
5
- describe '.new' do
6
- context 'with a Hash' do
5
+ describe ".new" do
6
+ context "with a Hash" do
7
7
  subject { EtcdDiscovery::Registrar.new "service", {"name" => "example.com", "ports" => {"http" => 80}} }
8
8
  it "should transform the has in Host" do
9
9
  expect(subject.host.attributes["name"]).to eq "example.com"
10
10
  end
11
11
  end
12
12
 
13
- context 'with a EtcdDiscovery::Host' do
13
+ context "with a EtcdDiscovery::Host" do
14
14
  subject { EtcdDiscovery::Registrar.new "service", EtcdDiscovery::Host.new({"name" => "example.com", "ports" => {"http" => 80}}) }
15
15
  it "should defines the state to new" do
16
16
  expect(subject.state).to eq :new
@@ -22,40 +22,81 @@ RSpec.describe EtcdDiscovery::Registrar do
22
22
  context "with an unregistered client" do
23
23
  subject { EtcdDiscovery::Registrar.new "service", EtcdDiscovery::Host.new({"name" => "example.com", "ports" => {"http" => 80}}) }
24
24
 
25
- describe '#stop' do
25
+ describe "#stop" do
26
26
  it "should raise an exception if service not registered" do
27
- expect{ subject.stop }.to raise_exception EtcdDiscovery::InvalidStateError
27
+ expect { subject.stop }.to raise_exception EtcdDiscovery::InvalidStateError
28
28
  end
29
29
  end
30
30
 
31
31
  its(:client) { is_expected.to eq EtcdDiscovery.config.client }
32
- its(:key) { is_expected.to eq "/services/service/example.com" }
32
+ its(:host_key) { is_expected.to start_with "/services/service/" }
33
33
  end
34
34
 
35
35
  context "with a registered client" do
36
- subject { EtcdDiscovery::Registrar.new("service", EtcdDiscovery::Host.new({"name" => "example.com", "ports" => {"http" => 80}})) }
36
+ let(:info) do
37
+ {
38
+ "name" => "service",
39
+ "critical" => true,
40
+ "hostname" => "public.scalingo.test",
41
+ "user" => "user",
42
+ "password" => "secret",
43
+ "ports" => {"https" => "5000", "http" => 80},
44
+ "public" => true
45
+ }
46
+ end
47
+ let(:host) do
48
+ {
49
+ "name" => info["hostname"],
50
+ "service_name" => info["name"],
51
+ "ports" => info["ports"],
52
+ "user" => info["user"],
53
+ "password" => info["secret"],
54
+ "public" => info["public"],
55
+ "private_ports" => info["ports"],
56
+ "critical" => info["critical"],
57
+ "private_hostname" => "private01.scalingo.test",
58
+ "uuid" => "my-uuid"
59
+ }
60
+ end
61
+ subject do
62
+ EtcdDiscovery::Registrar.new(
63
+ info["name"],
64
+ EtcdDiscovery::Host.new({"name" => info["hostname"], "ports" => info["ports"]})
65
+ )
66
+ end
37
67
 
38
- before(:each) do subject.register ; sleep 0.2 ; end
39
- after(:each) do subject.stop if subject.state == :started ; end
68
+ before(:each) do
69
+ mock_service_info(info["name"], info, true)
70
+ mock_hosts(info["name"], host, 1)
71
+ mock_set_service_key(info["name"])
72
+ mock_set_host_key(info["name"], subject.host.attributes["uuid"])
40
73
 
41
- describe '#register' do
42
- its(:thread) { is_expected.not_to eq nil }
43
- it "s thread should be alived" do
44
- expect(subject.thread.alive?).to eq true
45
- end
74
+ subject.register
75
+ end
76
+
77
+ after(:each) do
78
+ mock_delete_host_key(info["name"], subject.host.attributes["uuid"])
79
+ subject.stop if subject.state == :started
46
80
  end
47
81
 
48
82
  describe "#stop" do
49
83
  it "should stop its registering thread" do
84
+ mock_delete_host_key(info["name"], subject.host.attributes["uuid"])
85
+
50
86
  subject.stop
51
- sleep 0.2
87
+ # Wait for the register thread to detect the change of @state and stop.
88
+ sleep 0.3
52
89
  expect(subject.thread.alive?).to eq false
53
90
  end
54
91
 
55
- it "should remote the etcd key of the service" do
56
- expect(EtcdDiscovery.get("service").length).to eq 1
92
+ it "should set the service state to stopped" do
93
+ mock_service_info(info["name"], info)
94
+ mock_delete_host_key(info["name"], subject.host.attributes["uuid"])
95
+ expect(EtcdDiscovery.get(info["name"]).all.length).to eq 1
96
+
57
97
  subject.stop
58
- expect { EtcdDiscovery.get("service") }.to raise_exception EtcdDiscovery::ServiceNotFound
98
+
99
+ expect(subject.state).to eq :stopped
59
100
  end
60
101
  end
61
102
  end
data/spec/spec_helper.rb CHANGED
@@ -6,7 +6,7 @@ Bundler.require(:default, :development, :test)
6
6
 
7
7
  EtcdDiscovery.config.register_renew = 0.1
8
8
 
9
- require 'webmock/rspec'
9
+ require "webmock/rspec"
10
10
  WebMock.disable_net_connect!(allow_localhost: false)
11
11
 
12
12
  require_relative "./etcd_helper"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: etcd-discovery
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Léo Unbekandt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-09 00:00:00.000000000 Z
11
+ date: 2023-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: etcd
@@ -51,14 +51,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: 2.7.0
55
55
  required_rubygems_version: !ruby/object:Gem::Requirement
56
56
  requirements:
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
59
  version: '0'
60
60
  requirements: []
61
- rubygems_version: 3.0.3
61
+ rubygems_version: 3.3.5
62
62
  signing_key:
63
63
  specification_version: 4
64
64
  summary: Service discovery based on etcd