Coder Social home page Coder Social logo

wearefarmgeek / diplomat Goto Github PK

View Code? Open in Web Editor NEW
370.0 14.0 116.0 565 KB

A HTTP Ruby API for Consul

License: BSD 3-Clause "New" or "Revised" License

Ruby 99.86% Gherkin 0.14%
consul ruby diplomat consul-cluster rails cluster lock data-center api api-client

diplomat's Introduction

Diplomat

Build Status Gem Version Gem Code Climate Inline docs

A HTTP Ruby API for Consul

Diplomacy Board Game

FAQ

What's Diplomat for?

Diplomat allows any ruby application to interact with Consul's distributed key value store, and also receive information about services currently available in the Consul cluster.

Does it work in rails?

Yup! In fact, we're using it in all of our rails production apps instead of any previous case where it'd be right to use environment variables according to 12Factor configuration principals. This gives us the ability to scale up without making any changes to the actual project codebase, and to move applications around the cluster with ease.

Here's what a production database.yml file might look like:

<% if Rails.env.production? %>
production:
  adapter:            postgresql
  encoding:           unicode
  host:               <%= Diplomat::Service.get('postgres').Address %>
  database:           <%= Diplomat::Kv.get('project/db/name') %>
  pool:               5
  username:           <%= Diplomat::Kv.get('project/db/user') %>
  password:           <%= Diplomat::Kv.get('project/db/pass') %>
  port:               <%= Diplomat::Service.get('postgres').ServicePort %>
<% end %>

Why would I use Consul over ZooKeeper, Doozerd, etcd, Nagios, Sensu, SmartStack, SkyDNS, Chef, Puppet, Ansible, etc?

Read up what makes Consul different here

How do I install Consul?

See here. I managed to roll it out on my production machines with the help of Ansible in one working day.

Which versions of Ruby does Diplomat support? Where did my ruby 1.9 compatibility go?

Check out GitHub Actions to see which versions of ruby we currently test when we're making builds.

We've dropped ruby 1.9 support. You can still depend on Diplomat by directly using the ruby-1.9-compatible branch on github, although be advised it's not actively maintained anymore.

ERB templating

It is possible to inject diplomat data into .erb files (such as in chef), but you could also have a look at consul-templaterb that is highly optimized for ERB templating with very hi parallelism and good optimized performance for large clusters.

Usage

The most up to date place to read about the API is here.

Here's a few examples of how diplomat works:

Key Values

Setting

Setting the value of a key is easy as pie:

foo = Diplomat::Kv.put('foo', 'bar')
# => "bar"

Getting

Getting the value of a key is just as simple:

foo = Diplomat::Kv.get('foo')
# => "bar"

Or retrieve a value from another datacenter:

foo = Diplomat::Kv.get('foo', :dc => 'dc-west')
# => "baz"

You can also retrieve values recursively:

Diplomat::Kv.put('foo/a', 'lorem')
Diplomat::Kv.put('foo/b', 'ipsum')
Diplomat::Kv.put('foo/c', 'dolor')

Diplomat::Kv.get('foo/', recurse: true)
# => [{:key=>"foo/a", :value=>"lorem"}, {:key=>"foo/b", :value=>"ipsum"}, {:key=>"foo/c", :value=>"dolor"}]

You can also use get_all to retrieve values recursively with a consistent return type:

Diplomat::Kv.put('foo/a', 'lorem')
Diplomat::Kv.put('foo/b', 'ipsum')
Diplomat::Kv.put('foo/c', 'dolor')

Diplomat::Kv.get('foo/', recurse: true)
# => [{:key=>"foo/a", :value=>"lorem"}, {:key=>"foo/b", :value=>"ipsum"}, {:key=>"foo/c", :value=>"dolor"}]
Diplomat::Kv.get_all('foo/')
# => [{:key=>"foo/a", :value=>"lorem"}, {:key=>"foo/b", :value=>"ipsum"}, {:key=>"foo/c", :value=>"dolor"}]

Diplomat::Kv.put('bar/a', 'lorem')

Diplomat::Kv.get('bar/', recurse: true)
# => "lorem"
Diplomat::Kv.get_all('bar/')
# => [{:key=>"bar/a", :value=>"lorem"}]

Or list all available keys:

Diplomat::Kv.get('/', :keys => true) # => ['foo/a', 'foo/b']

You can convert the consul data to a ruby hash

Diplomat::Kv.put('foo/a', 'lorem')
Diplomat::Kv.put('foo/b', 'ipsum')
Diplomat::Kv.put('foo/c', 'dolor')

Diplomat::Kv.get('foo/', recurse: true, convert_to_hash: true)
# => {"foo"=>{"a"=>"lorem", "b"=>"ipsum", "c"=>"dolor"}}

Nodes

Getting

Look up a node:

foo_service = Diplomat::Node.get('foo')
# => {"Node"=>{"Node"=>"foobar", "Address"=>"10.1.10.12"}, "Services"=>{"consul"=>{"ID"=>"consul", "Service"=>"consul", "Tags"=>nil, "Port"=>8300}, "redis"=>{"ID"=>"redis", "Service"=>"redis", "Tags"=>["v1"], "Port"=>8000}}}

Get all nodes:

nodes = Diplomat::Node.get_all
# => [#<OpenStruct Address="10.1.10.12", Node="foo">, #<OpenStruct Address="10.1.10.13", Node="bar">]

Get all nodes for a particular datacenter

nodes = Diplomat::Node.get_all({ :dc => 'My_Datacenter' })
# => [#<OpenStruct Address="10.1.10.12", Node="foo">, #<OpenStruct Address="10.1.10.13", Node="bar">]

Register a node:

Diplomat::Node.register({ :Node => "app1", :Address => "10.0.0.2" })
# => true

De-register a node:

Diplomat::Node.deregister({ :Node => "app1", :Address => "10.0.0.2" })
# => true

Services

Getting

Looking up a service is easy as pie:

foo_service = Diplomat::Service.get('foo')
# => #<OpenStruct Node="hotel", Address="1.2.3.4", ServiceID="hotel_foo", ServiceName="foo", ServiceTags=["foo"], ServicePort=5432>

Or if you have multiple nodes per service:

foo_service = Diplomat::Service.get('foo', :all)
# => [#<OpenStruct Node="hotel", Address="1.2.3.4", ServiceID="hotel_foo", ServiceName="foo", ServiceTags=["foo"], ServicePort=5432>,#<OpenStruct Node="indigo", Address="1.2.3.5", ServiceID="indigo_foo", ServiceName="foo", ServiceTags=["foo"], ServicePort=5432>]

Or if you want to find services for a particular datacenter

foo_service = Diplomat::Service.get('foo', :all, { :dc => 'My_Datacenter'})
# => [#<OpenStruct Node="hotel", Address="1.2.3.4", ServiceID="hotel_foo", ServiceName="foo", ServiceTags=["foo"], ServicePort=5432>,#<OpenStruct Node="indigo", Address="1.2.3.5", ServiceID="indigo_foo", ServiceName="foo", ServiceTags=["foo"], ServicePort=5432>]

Or if you want to filter services

foo_service = Diplomat::Service.get('foo', :all, { :filter => 'ServiceID == "indigo_foo"'})
# => [#<OpenStruct Node="indigo", Address="1.2.3.5", ServiceID="indigo_foo", ServiceName="foo", ServiceTags=["foo"], ServicePort=5432>]

If you wish to list all the services on consul:

services = Diplomat::Service.get_all
# => #<OpenStruct consul=[], foo=[], bar=[]>

If you wish to list all the services for a specific datacenter:

services = Diplomat::Service.get_all({ :dc => 'My_Datacenter' })
# => #<OpenStruct consul=[], foo=[], bar=[]>

Datacenters

Getting a list of datacenters is quite simple and gives you the option to extract all services out of all accessible datacenters if you need to.

datacenters = Diplomat::Datacenter.get()
# => ["DC1", "DC2"]

Sessions

Creating a session:

sessionid = Diplomat::Session.create({:Node => "server1", :Name => "my-lock"})
# => "fc5ca01a-c317-39ea-05e8-221da00d3a12"

Or destroying a session:

Diplomat::Session.destroy("fc5ca01a-c317-39ea-05e8-221da00d3a12")

Renew a session:

Diplomat::Session.renew(sessionid)

List sessions:

Diplomat::Session.list.each {|session| puts "#{session["ID"]} #{session["Name"]}"}

Locks

Acquire a lock:

sessionid = Diplomat::Session.create({:Node => "server1", :Name => "my-lock"})
lock_acquired = Diplomat::Lock.acquire("/key/to/lock", sessionid)
# => true

Or wait for a lock to be acquired:

sessionid = Diplomat::Session.create({:hostname => "server1", :ipaddress => "4.4.4.4"})
lock_acquired = Diplomat::Lock.wait_to_acquire("/key/to/lock", sessionid)

Release a lock:

Diplomat::Lock.release("/key/to/lock", sessionid )

Events

Fire an event:

Diplomat::Event.fire('do_something', 'payload')

List all events with a certain name received by the local agent:

Diplomat::Event.get_all('do_something')

Get the latest event with a certain name received by the local agent:

Diplomat::Event.get('do_something')

Iterate through the events with a certain name received by the local agent:

events = Enumerator.new do |y|
  ret = {token: :first}
  while ret = begin Diplomat::Event.get('do_something', ret[:token], :reject) rescue nil end
    y.yield(ret[:value])
  end
end

events.each{ |e| puts e }

Status

Returns information about the status of the Consul cluster.

Get the raft leader for the datacenter in which the local consul agent is running

Diplomat::Status.leader()

Get an array of Raft peers for the datacenter in which the agent is running

Diplomat::Status.peers()

Autopilot

Returns information about the autopilot configuration of the Consul cluster

Get the current autopilot configuration

Diplomat::Autopilot.get_configuration()

Get the health status from autopilot

Diplomat::Autopilot.get_health()

Health

Retrieve health of a node

Diplomat::Health.node('fooNode', :dc => 'abc')

Retrieve health of a given check

Diplomat::Health.checks('fooCheck', :dc => 'abc')

Retrieve health of a given service

Diplomat::Health.service('fooService', :dc => 'abc')

Retrieve a list of anything that correspond to the state ("any", "passing", "warning", or "critical") You can use filters too !

Diplomat::Health.state("critical", {:dc => 'abc', :filter => 'Node==foo'})

You also have some convenience method (any, passing, warning, critical) That can be filtered

Diplomat::Health.critical({:dc => 'abc', :filter => 'ServiceName==foo'})

Maintenance mode

Enable maintenance mode on a host, with optional reason and DC (requires access to local agent)

Diplomat::Maintenance.enable(true, 'doing stuff', :dc => 'abc')

Determine if a host has maintenance mode enabled

Diplomat::Maintenance.enabled('foobar')
# => { :enabled => true, :reason => 'doing stuff' }

Custom configuration

You can create a custom configuration using the following syntax:

Diplomat.configure do |config|
  # Set up a custom Consul URL
  config.url = "http://localhost:8888"
  # Set up a custom Faraday Middleware
  config.middleware = MyCustomMiddleware
  # Set extra Faraday configuration options and custom access token (ACL)
  config.options = {ssl: {version: :TLSv1_2}, headers: {"X-Consul-Token" => "xxxxxxxx-yyyy-zzzz-1111-222222222222"}}
end

This is traditionally kept inside the config/initializers directory if you're using rails. The middleware allows you to customise what happens when faraday sends and receives data. This can be useful if you want to instrument your use of diplomat, for example. You can read more about Faraday's custom middleware here.

Alternatively, configuration settings can be overriden at each method call allowing for instance to address different consul agents, with some other token.

Diplomat::Service.get('foo', { http_addr: 'http://consu01:8500' })
Diplomat::Service.get('foo', { http_addr: 'http://consu02:8500' })
Diplomat::Kv.put('key/path', 'value', { http_addr: 'http://localhost:8500', dc: 'dc1', token: '111-222-333-444-555' })

Most common options are:

  • dc: target datacenter
  • token: identity used to perform the corresponding action
  • http_addr: to target a remote consul node
  • stale: use consistency mode that allows any server to service the read regardless of whether it is the leader

Todo

  • Updating Docs with latest changes
  • Using custom objects for response objects (instead of openStruct)
  • PUTing and DELETEing services
  • Custom SSL Cert Middleware for faraday
  • Allowing the custom configuration of the consul url to connect to
  • Deleting Keys
  • Listing available services
  • Health
  • Members
  • Status
  • Datacenter support for services
  • Ruby 1.8 support
  • Events

Enjoy!

Photo Copyright "merlinmann" https://www.flickr.com/photos/merlin/. All rights reserved.

diplomat's People

Contributors

aaronbbrown avatar annih avatar awentzonline avatar beso323 avatar brugidou avatar chaos95 avatar cory-stripe avatar damfle avatar dig12345 avatar drexciter avatar frightenedmonkey avatar galthaus avatar hunter avatar jebentier avatar johnhamelink avatar kamaradclimber avatar kstiert avatar lennartbuit avatar mrcroy avatar mt-inside avatar olleolleolle avatar peterfcaswell avatar piavlo avatar pierresouchay avatar plugin73 avatar sandstrom avatar scalp42 avatar taharah avatar tionebsalocin avatar warmfusion avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

diplomat's Issues

Kv.get is not idiomatic ruby

This calling convention is painful:

Diplomat::Kv.get(KEY_PREFIX, {recurse: true}, :return)

The ruby interpreter expects to find a hash as the last option. It would be far better to roll the not_found and found options into the options hash itself and grab them with Hash#delete.

Example call site:

Diplomat::Kv.get(KEY_PREFIX, recurse: true, not_found: :return)

It's also surprising that get returns an empty string instead of nil when the key is not found in not_found = :return mode.

I'm not sure what the way forward is since there is a ton of existing code that relies on the existing behavior (including mine).

Blocking Kv query throwing exception

I am trying to do a blocking Kv query and I get the following exception.

changed = Diplomat::Kv.get(prefix, { recurse: true }, :wait, :wait)

<NoMethodError> undefined method `first' for nil:NilClass
    /Users/athompson/code/daemon_runner/vendor/gems/diplomat-1.0.0/lib/diplomat/rest_client.rb:96:in `return_value'
    /Users/athompson/code/daemon_runner/vendor/gems/diplomat-1.0.0/lib/diplomat/kv.rb:99:in `get'
    /Users/athompson/code/daemon_runner/vendor/gems/diplomat-1.0.0/lib/diplomat/rest_client.rb:46:in `method_missing'

Releases and tagging

It would be very helpful if tags/releases were created when a new version of the gem was cut.

service and node register/deregister do not use supplied acl_token

It seems that a few Diplomat functions do not use the acl_token that is set during configuration.

Looking at the register method definition from service.rb

def register(definition, path='/v1/agent/service/register')
json_definition = JSON.dump(definition)
register = @conn.put path, json_definition
return register.status == 200
end

I see that the acl_token definition is missing because it is neither part of query string not set as a http header. This means that service and node registrations/deregistartions can not happen when acls are in play using Diplomat.

I have temporarily fixed this by modifying the register definition per below - but this may not be the most optimal way given the rest of the code flow

def register(definition, path='/v1/agent/service/register')
  path += "?#{check_acl_token[0]}" unless check_acl_token.empty?
  json_definition = JSON.dump(definition)
  register = @conn.put path, json_definition
  return register.status == 200
end

Most of the other put action use different syntax that adds the acl_token if it exists.

I was hoping this is something that you may want to fix and push push out a new gem. Thanks!

Diplomat::Acl.info(uuid) raise if acl does not exist

When calling Diplomat::Acl.info(a_uuid), the method raise an exception if the acl does not exist.
The expected output is to return nil instead.

With consul 0.5.2:

JSON::ParserError: 757: unexpected token at 'null'
from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/json-1.8.3/lib/json/common.rb:155:in parse' from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/json-1.8.3/lib/json/common.rb:155:inparse'
from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/diplomat-0.15.0/lib/diplomat/rest_client.rb:76:in parse_body' from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/diplomat-0.15.0/lib/diplomat/acl.rb:30:ininfo'
from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/diplomat-0.15.0/lib/diplomat/rest_client.rb:46:in method_missing' from (irb):9 from /opt/chef/embedded/bin/irb:11:in

'

In this case, the body is the string "null" instead of being empty/nil.

Feature: Service rotation

In the same way consul DNS round robins services by IP address, it would be nice if diplomat would rotate all the available services for one service name (in this case, round robining w/ both IP and port rather than just IP as consul's DNS works).

Diplomat::Service.next('myservice')

Randomize service results

Consul returns http service calls in a consistent order, when 'usually' one doesn't want all clients to use the same instance. When requesting just one instance of a service, return a random instance from the list of results.

Option to specify logger

The following line:
https://github.com/WeAreFarmGeek/diplomat/blob/master/lib/diplomat/rest_client.rb#L18
sends all output to STDOUT.

That's all well and good in a Rails app, but when running from a commandline Ruby script this pollutes the output to the terminal. It would be great if there was a way to specify the log output to go elsewhere, or at the very least suppress it entirely.

Todo:

  • Add Initializer config
  • Add option to modify consul URL
  • Add option to specify logger

Service requests for Invalid DataCenters throw unhelpful exceptions

When trying to get a list of services for an explicit datacenter, I found if the DC wasn't found, or otherwise invalid, the service request throws a 500 error and doesn't give any useful feedback.

Could this be setup to be more explicit Diplomat/Consul related exception ?

irb(main):006:0> Diplomat::Service.get('redis?dc=no_such_dc')
Faraday::ClientError: the server responded with status 500
    from /var/lib/gems/1.9.1/gems/faraday-0.9.1/lib/faraday/response/raise_error.rb:13:in `on_complete'
    from /var/lib/gems/1.9.1/gems/faraday-0.9.1/lib/faraday/response.rb:9:in `block in call'
    from /var/lib/gems/1.9.1/gems/faraday-0.9.1/lib/faraday/response.rb:57:in `on_complete'
    from /var/lib/gems/1.9.1/gems/faraday-0.9.1/lib/faraday/response.rb:8:in `call'
    from /var/lib/gems/1.9.1/gems/faraday-0.9.1/lib/faraday/request/url_encoded.rb:15:in `call'
    from /var/lib/gems/1.9.1/gems/faraday-0.9.1/lib/faraday/adapter/net_http.rb:56:in `call'
    from /var/lib/gems/1.9.1/gems/faraday-0.9.1/lib/faraday/rack_builder.rb:139:in `build_response'
    from /var/lib/gems/1.9.1/gems/faraday-0.9.1/lib/faraday/connection.rb:377:in `run_request'
    from /var/lib/gems/1.9.1/gems/faraday-0.9.1/lib/faraday/connection.rb:140:in `get'
    from /var/lib/gems/1.9.1/gems/diplomat-0.12.0/lib/diplomat/service.rb:22:in `get'
    from /var/lib/gems/1.9.1/gems/diplomat-0.12.0/lib/diplomat/rest_client.rb:46:in `method_missing'
    from (irb):6
    from /usr/bin/irb:12:in `<main>'

Inconsistent handling of 403 responses

When you have a consul ACL in place, the exceptions your get from diplomat are inconsistent:

E.g. Diplomat::Acl.list raises

Diplomat::UnknownStatus: status 403

while Diplomat::Acl.create({}) raises

Faraday::ClientError: the server responded with status 403

which makes it messy to catch the exceptions and determine if authentication is needed.

performace issue

I read the service.rb code, It seems that It does send http request to get server list every time

foo_service = Diplomat::Service.get('foo')

is invoked.

If I have a application with high traffic. does it will cause high pressure to consul cluster?

why don't cache the service url and its server list and start a daemon thread to update it?

404 and Exception if key is missing

At the moment a Diplomat.get('foo') raise an exception if the 'foo' key does not exist in the key/value store.

This looks like an odd behaviour as I was expecting a nil value (as would Redis or Memcached do for example).

I understand #16 do some work around this very thing but does not seem to change the end behavior. Is there any reason why the exception is raised rather than returning a nil value ?

Unable to subscribe to event stream with Diplomat::Event

For my use case I want to treat the consul events like an event stream and get the newest one or sleep until a new event arrives. However, both .get and .get_all appear to drop the current newest event if run with :wait, :wait. This means that I can either get the latest event or choose to possibly drop an event and wait for a new one.

Here's some ruby code that does what I want:

response = Excon.get(
  URL,
  query: {name: EVENT},
  expects: [200],
  connect_timeout: 5,
  read_timeout: 5,
  write_timeout: 5,
  tcp_nodelay: true
)

handle_events(response.body)

loop do
  index = response.headers['X-Consul-Index']
  response = Excon.get(
    URL,
    query: {name: EVENT, index: index},
    expects: [200],
    connect_timeout: 5,
    read_timeout: 86400,
    write_timeout: 5,
    tcp_nodelay: true
  )

  handle_events(response.body)
end

I then sift the newest event against a consul key that stores the last event LTime I worked off.

I expected to be able to use Diplomat to solve this problem.

Faraday gem breaks SSL connections on 0.6.1

Hey guys, first I got to say that it's a great gem!
Unfortunately I found a bug that I can not pinpoint exactly what causes it, in my opinion it's related to Faraday but I couldn't find the commit that updated the Faraday gem.

What basically happens is that when I'm using version 0.5.1 I am able to use Diplomat with a middleware to talk to consul over SSL but with 0.6.1 I can not.

I'm using a self-signed ssl using a CA I created, used this tutorial to get it running,
https://www.digitalocean.com/community/tutorials/how-to-secure-consul-with-tls-encryption-on-ubuntu-14-04

Steps to reproduce:

# config.json
{
  "server": true,
  "ports": {
    "dns":  -1,
    "https": 8500,
    "http": -1
  },
  "encrypt": "NH4Ykhc4aqzdsSO8Rduchg==",
  "ca_file": "/src/research/diplomat/ca.cert",
  "cert_file": "/src/research/diplomat/consul.cert",
  "key_file": "/src/research/diplomat/consul.key",
  "verify_incoming": true,
  "verify_outgoing": true
}
# test.rb
require 'rubygems'
require 'diplomat'

class DiplomatSSLMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    env.ssl = {
      ca_file:     "/src/research/diplomat/ca.cert",
      client_cert: OpenSSL::X509::Certificate.new(File.read("/src/research/diplomat/consul.cert")),
      client_key:  OpenSSL::PKey::RSA.new(File.read("/src/research/diplomat/consul.key"))
    }

    @app.call(env)
  end
end

Diplomat.configure do |config|
  config.url        = "https://local.example.com:8500"
  config.middleware = DiplomatSSLMiddleware
end

Diplomat.get('test')

I created a SSL certificate for local.exampe.com and edited my local /etc/hosts to pinpoint it to localhost.

Running in a terminal window the following command to have a consul server running
consul agent -data-dir=/src/research/diplomat -server -bootstrap -config-file=/src/research/diplomat/config.json

Running bundle exec ruby test.rb

On 0.6.1:

SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (Faraday::SSLError)

On 0.5.1:
the server responded with status 404 (Faraday::ResourceNotFound)

  • This is okay because I haven't set the key

v0.7.0 breaks Diplomat::Service.get(...)

Hi -

Starting with v0.7.0, the following breaks:

require 'diplomat'
Diplomat::Service.get('mysql').Address

You get the following error:

ArgumentError: wrong number of arguments (0 for 1..4)
   from /var/lib/gems/2.0.0/gems/diplomat-0.7.0/lib/diplomat/service.rb:13:in `get'
    from /var/lib/gems/2.0.0/gems/diplomat-0.7.0/lib/diplomat/service.rb:47:in `get'

After downgrading to v0.6.1 the error disappeared.

Update health api to support query args

I am trying to use the service health api and noticed it doesn't support any of the query args[1]. I'm trying to use this to get healthy instances of a service. Or is there a better way to do this with diplomat?

I'd be happy to help out with this. It looks like this might end up looking like Node.get[2] or Service.get[3].

[1] https://consul.io/docs/agent/http/health.html
[2]

# Get a node by it's key
# @param key [String] the key
# @param options [Hash] :dc string for dc specific query
# @return [OpenStruct] all data associated with the node
def get key, options=nil
url = ["/v1/catalog/node/#{key}"]
url << use_named_parameter('dc', options[:dc]) if options and options[:dc]
# If the request fails, it's probably due to a bad path
# so return a PathNotFound error.
begin
ret = @conn.get concat_url url
rescue Faraday::ClientError
raise Diplomat::PathNotFound
end
return JSON.parse(ret.body)
end

[3]
# Get a service by it's key
# @param key [String] the key
# @param scope [Symbol] :first or :all results
# @param options [Hash] :wait string for wait time and :index for index of last query
# @param meta [Hash] output structure containing header information about the request (index)
# @return [OpenStruct] all data associated with the service
def get key, scope=:first, options=nil, meta=nil
url = ["/v1/catalog/service/#{key}"]
url << use_named_parameter('wait', options[:wait]) if options and options[:wait]
url << use_named_parameter('index', options[:index]) if options and options[:index]
url << use_named_parameter('dc', options[:dc]) if options and options[:dc]
url << use_named_parameter('tag', options[:tag]) if options and options[:tag]
# If the request fails, it's probably due to a bad path
# so return a PathNotFound error.
begin
ret = @conn.get concat_url url
rescue Faraday::ClientError
raise Diplomat::PathNotFound
end
if meta and ret.headers
meta[:index] = ret.headers["x-consul-index"]
meta[:knownleader] = ret.headers["x-consul-knownleader"]
meta[:lastcontact] = ret.headers["x-consul-lastcontact"]
end
if scope == :all
return JSON.parse(ret.body).map { |service| OpenStruct.new service }
end
return OpenStruct.new JSON.parse(ret.body).first
end

Provide all responses for service.get

When running Diplomat::Service.get() only the first result is returned.
It would be handy if there was a way to return all services as provided by the returned JSON from the API.

Maybe in the interests of backward compatibility an additional "all" parameter could be required in order to do this? Thoughts?

please consider my enhancements for inclusion in your next gem version

Hello John! Thanks so much for your nicely done diplomat gem. I've begun using it, along with consul and consul-template, and am very happy. I wrote a new class, and a few new methods, that I'd like you to review for inclusion. Sorry - my workstation is old, so I'm not setup to do pull requests, and haven't begun writing tests yet... that's the next hurdle I hope to attack shortly.

In the meantime, you can find my proposed files here:

http://speak.smashrun.com/assets/images/dip.tgz

I tried to keep your general sense of things. You'll immediately see the new "key_exist" method, and the new "Agent" class, which I added due to the /v1/agent/ API structure.

let me know what you think! Thanks again.

Steve
@cixelsyd

JSON parse error from Diplomat::Status.leader()

I have a single test server. When using Diplomat::Status.leader(), it raises an exception.

Consul 0.7.0
Diplomat 2.0.1

$ curl -XGET http://localhost:8500/v1/status/leader
"10.0.2.15:8300"
irb(main):001:0> require 'diplomat'
=> true
irb(main):007:0> Diplomat::Status.leader()
JSON::ParserError: 784: unexpected token at '"10.0.2.15:8300"'
	from /usr/lib/ruby/2.3.0/json/common.rb:156:in `parse'
	from /usr/lib/ruby/2.3.0/json/common.rb:156:in `parse'
	from /var/lib/gems/2.3.0/gems/diplomat-2.0.1/lib/diplomat/status.rb:11:in `leader'
	from /var/lib/gems/2.3.0/gems/diplomat-2.0.1/lib/diplomat/rest_client.rb:43:in `method_missing'
	from (irb):7
	from /usr/bin/irb:11:in `<main>'

Service lookup is not idiomatic ruby

Currently, a service lookup goes like this:

mail_service = Diplomat::Service.get('mail')
mail_service.inspect # <OpenStruct Node="i-0c9693e4", Address="10.10.20.220", ServiceID="mail", ServiceName="mail", ServiceTags=[], ServiceAddress="", ServicePort=8080, ServiceEnableTagOverride=false, CreateIndex=4398409, ModifyIndex=4549606>

host, port = sage_service.Address, sage_service.ServicePort

This syntax is unlike anything else in ruby, where properties and methods are snake_cased and only classes are CamelCased.

One would expect something like this:

mail_service = Diplomat::Service.get('mail')
mail_service.inspect # <OpenStruct node="i-0c9693e4", address="10.10.20.220", service_id="mail", service_name="mail", service_tags=[], service_address="", service_port=8080, service_enable_tag_override=false, create_index=4398409, modify_index=4549606>

host, port = sage_service.address, sage_service.service_port

Overall Diplomat is excellent! โ›ต๏ธ

But this part could use some adjustments to make it align with all of Rubys standard-library plus basically all other gems.


A related method naming issue aside: since Service.get is called it feels a bit tautological to then call the method service_port instead of just port (same for service_tags, service_name, etc).

LoadError: cannot load such file -- response/raise_error

Using diplomat v0.15.0 and Rails v4.2.5.2 in a console session running:

pry(main)> Diplomat::Service.get('api')
LoadError: cannot load such file -- response/raise_error
from /Users/jfilipe/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require'

I've traced the exception to this line

faraday.response :raise_error unless raise_error
.
For some reason it cannot find faraday's RaiseError exception class.

If I update the above line to the following, everything works properly:

faraday.use Faraday::Response::RaiseError unless raise_error

Diplomat::Kv.get("unexistent_key", nil, :wait, :wait) raises an error when key does not exist

Trying to fetch a non existent key from Consul's Key/Value store using the following:

Diplomat::Kv.get("unexistent_key", nil, :wait, :wait)

Raises the following error:
Faraday::ResourceNotFound: the server responded with status 404

From the Diplomat::Kv.get documentation:
@param not_found [Symbol] behaviour if the key doesn't exist; # :reject with exception, :return degenerate value, or :wait for it to appear

The behaviour should've been to keep blocking until the key exists.
Checking out the code, it seems like the problem originates from Diplomat::RestClient#start_connection (lib/diplomat/rest_client.rb):

    def start_connection api_connection=nil
      @conn = build_connection(api_connection)
      @conn_no_err = build_connection(api_connection, true)
    end

    def build_connection(api_connection, raise_error=false)

@conn_no_error is being set to a Faraday client which DOES raise an error (notice the second argument which is set to true for build_connection) as opposed to @conn.

This is directly connected to:
lib/diplomat/kv.rb

    def get key, options=nil, not_found=:reject, found=:return
      @key = key
      @options = options

      url = ["/v1/kv/#{@key}"]
      url += check_acl_token
      url += use_consistency(@options)

      # 404s OK using this connection
      raw = @conn_no_err.get concat_url url  # <= This statement raises the error
      if raw.status == 404

@conn_no_err.get concat_url url in the above raises the error since the Faraday client is set to raise errors.

Update maintenance endpoint

The maintenance class is missing some of the available calls as well as should be updated to use the agent API endpoint.

Feature: allow to specify an 'index' on Diplomat::Kv.get

Current Diplomat::Kv.get implementation does not allow to specify an ?index param for fetching a key value if index's value is smaller than the current key's ModifyIndex and "wait" (block) if it equals to it.
This is extremely useful for implementing a watch like functionality which avoids polling.

Error in session renew

The session can't be renewed. I looks like a typo on line 47 of session.rb.

Currently:
req.url = "/v1/session/renew/#{id}"

and should be:
req.url "/v1/session/renew/#{id}"

Update README

The README is currently very out of date and missing several of the now available endpoints.

Unable to compile the json gem on production machines

I am wanting to use this library with Puppet, but we don't want to have to compile native extensions on our production machines before puppet can run. This is currently required for us to use diplomat, because of the dependency on the json gem.

Would it be possible to move to using json_pure instead?

Kv.get /w recurse: true is not consistent

Kv.get with recurse: true is not consistent if there is only single key were returned.

I am expecting array of key/values, but if there is only single key it would return just its value.

Diplomat::Kv.put('hello/world', 'test')

Diplomat::Kv.get('hello', recurse: true)
# current:
# => 'test'
# expected
# => [{ key: 'hello/world', value: 'test'}]

Current workaround is specifying decode_values: true:

Diplomat::Kv.get('hello', recurse: true, decode_values: true)
# => {[ 'Key' => 'hello/world', 'Value' => 'test', ... }]

But there is no way to make convert_to_hash to work with single key atm.

Can't set development environment with remote URL

I don't have consol setup in development environment, so I want to use remote URL .. but seems like it doesn't work ..

Cannot load Rails.application.database_configuration: (Faraday::ConnectionFailed)
Connection refused - connect(2) for "localhost" port 8500

Using (or not) proxies with Diplomat

We have a weird situation in that we need to use a proxy for some traffic and not for other traffic.

Diplomat (or farady in this case?) seems to pick up my ENV["HTTP_PROXY"] variables and is using that; I want diplomat to NOT use the proxy.

Is there a way to easily disable the proxy?

Aborted transactions can crash Diplomat

My code tests fail with:

       expected no Exception, got #<NoMethodError: undefined method `empty?' for nil:NilClass> with backtrace:
         # ./.bundle/ruby/2.3.0/bundler/gems/diplomat-dd4eb143e598/lib/diplomat/kv.rb:242:in `decode_transaction'
         # ./.bundle/ruby/2.3.0/bundler/gems/diplomat-dd4eb143e598/lib/diplomat/kv.rb:237:in `transaction_return'
         # ./.bundle/ruby/2.3.0/bundler/gems/diplomat-dd4eb143e598/lib/diplomat/kv.rb:174:in `txn'
         # ./.bundle/ruby/2.3.0/bundler/gems/diplomat-dd4eb143e598/lib/diplomat/rest_client.rb:43:in `method_missing'

I instrumented the failing line:

    237: def transaction_return(raw_return, options)
 => 238:   binding.pry
    239:   decoded_return =
    240:     options && options[:decode_values] == false ? raw_return : decode_transaction(raw_return)
    241:   OpenStruct.new decoded_return
    242: end

and saw this:

[1] pry(#<Diplomat::Kv>)> raw_return
=> {"Results"=>nil,
 "Errors"=>
  [{"OpIndex"=>1, "What"=>"failed index check for key \"our_path\", current modify index 8 != 7"}]}

Suggested fix is #133.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.