diff --git a/CHANGELOG.md b/CHANGELOG.md index 55bdd59..a625574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ # Next +- 2018-01-26 Nate St. Germain (@rockpapergoat) Add option to search all nodes by metadata + Pass a `:meta` hash as an option to `Diplomat::Node.get_all` to filter on metadata. + - 2017-11-09 Josep M. Blanquer (@blanquer) Fix service deregister to use the proper verb (`PUT` instead of `GET`). Consul 1.x seems to have started enforcing [it](https://www.consul.io/docs/upgrade-specific.html#http-verbs-are-enforced-in-many-http-apis). diff --git a/README.md b/README.md index ede8962..5abde31 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,14 @@ Get all nodes for a particular datacenter nodes = Diplomat::Node.get_all({ :dc => 'My_Datacenter' }) # => [#, #] ``` +Get all nodes with specified metadata + +NOTE: This requires adding metadata to nodes, either as part of the agent config or via the catalog. Adding multiple metadata attributes effectively is an `AND` operation here. + +```ruby +nodes = Diplomat::Node.get_all({ :meta => {'role' => 'server', 'availability_zone' => 'us-east-1d'}) +# => [#"10.10.10.210", "wan"=>"10.10.10.210"}, Meta={"availability_zone"=>"us-east-1d", "consul-network-segment"=>"", "environment"=>"dev", "instance_type"=>"t2.small", "ip"=>"10.10.10.210", "lsb_release"=>"16.04", "role"=>"server", "tags"=>""}>] +``` Register a node: @@ -180,6 +188,14 @@ services = Diplomat::Service.get_all({ :dc => 'My_Datacenter' }) # => # ``` +To list all the services associated with specific node metadata: + +```ruby +services = Diplomat::Service.get_all({ :meta => {'lsb_release' => '16.04', 'availability_zone' => 'us-east-1a'} }) +# => # +``` + + ### Datacenters Getting a list of datacenters is quite simple and gives you the option to extract all services out of diff --git a/Rakefile b/Rakefile index 5d0bd3d..a285e3a 100644 --- a/Rakefile +++ b/Rakefile @@ -33,7 +33,7 @@ end desc 'Run a bootstrapped consul server for testing' task :consul do - system('consul agent -server -bootstrap -data-dir=/tmp') + system('consul agent -dev -bootstrap -data-dir=/tmp') end task default: %w[style spec] diff --git a/lib/diplomat/kv.rb b/lib/diplomat/kv.rb index 64c070b..9db65d6 100644 --- a/lib/diplomat/kv.rb +++ b/lib/diplomat/kv.rb @@ -256,7 +256,7 @@ def decode_transaction(transaction) # rubocop:disable Metrics/MethodLength next unless resp['KV']['Value'] begin resp['KV']['Value'] = Base64.decode64(resp['KV']['Value']) - rescue # rubocop:disable RescueWithoutErrorClass + rescue StandardError nil end end diff --git a/lib/diplomat/node.rb b/lib/diplomat/node.rb index f29af90..2e8ed7d 100644 --- a/lib/diplomat/node.rb +++ b/lib/diplomat/node.rb @@ -5,7 +5,7 @@ class Node < Diplomat::RestClient @access_methods = %i[get get_all register deregister] - # Get a node by it's key + # Get a node by its key # @param key [String] the key # @param options [Hash] :dc string for dc specific query # @return [OpenStruct] all data associated with the node @@ -23,11 +23,17 @@ def get(key, options = nil) end # Get all the nodes - # @param options [Hash] :dc string for dc specific query + # @param options [Hash] + # :dc string for dc specific query + # :meta hash for metadata query # @return [OpenStruct] the list of all nodes - def get_all(options = nil) + def get_all(options = nil) # rubocop:disable Metrics/AbcSize url = ['/v1/catalog/nodes'] url << use_named_parameter('dc', options[:dc]) if options && options[:dc] + if options && options[:meta] + url << options[:meta].map { |m| use_named_parameter('node-meta', m.join(':')) }.flatten + Diplomat.configure { |c| c.options = { request: { params_encoder: Faraday::FlatParamsEncoder } } } + end ret = @conn.get concat_url url JSON.parse(ret.body).map { |service| OpenStruct.new service } diff --git a/lib/diplomat/rest_client.rb b/lib/diplomat/rest_client.rb index efbee4a..d5fd8fe 100644 --- a/lib/diplomat/rest_client.rb +++ b/lib/diplomat/rest_client.rb @@ -3,7 +3,7 @@ module Diplomat class RestClient @access_methods = [] - # Initialize the fadaray connection + # Initialize the faraday connection # @param api_connection [Faraday::Connection,nil] supply mock API Connection def initialize(api_connection = nil) start_connection api_connection @@ -143,7 +143,7 @@ def decode_values @raw.each_with_object([]) do |acc, el| begin acc['Value'] = Base64.decode64(acc['Value']) - rescue # rubocop:disable RescueWithoutErrorClass + rescue StandardError nil end el << acc diff --git a/lib/diplomat/service.rb b/lib/diplomat/service.rb index 463607c..fc6a41b 100644 --- a/lib/diplomat/service.rb +++ b/lib/diplomat/service.rb @@ -5,7 +5,7 @@ class Service < Diplomat::RestClient @access_methods = %i[get get_all register deregister register_external deregister_external maintenance] - # Get a service by it's key + # Get a service by its key # @param key [String] the key # @param scope [Symbol] :first or :all results # @param options [Hash] options parameter hash @@ -47,12 +47,18 @@ def get(key, scope = :first, options = nil, meta = nil) # rubocop:enable PerceivedComplexity, MethodLength, CyclomaticComplexity, AbcSize # Get all the services - # @param options [Hash] :dc Consul datacenter to query + # @param options [Hash] + # :dc Consul datacenter to query + # :meta hash for metadata query # @return [OpenStruct] the list of all services - def get_all(options = nil) + def get_all(options = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength url = ['/v1/catalog/services'] url += check_acl_token url << use_named_parameter('dc', options[:dc]) if options && options[:dc] + if options && options[:meta] + url << options[:meta].map { |m| use_named_parameter('node-meta', m.join(':')) } + Diplomat.configure { |c| c.options = { request: { params_encoder: Faraday::FlatParamsEncoder } } } + end begin ret = @conn.get concat_url url rescue Faraday::ClientError diff --git a/lib/diplomat/version.rb b/lib/diplomat/version.rb index 2ba2df7..8ad9589 100644 --- a/lib/diplomat/version.rb +++ b/lib/diplomat/version.rb @@ -1,3 +1,3 @@ module Diplomat - VERSION = '2.0.2'.freeze + VERSION = '2.0.3'.freeze end diff --git a/spec/node_spec.rb b/spec/node_spec.rb index dbd4470..e5b4a2c 100644 --- a/spec/node_spec.rb +++ b/spec/node_spec.rb @@ -58,6 +58,26 @@ } ] end + let(:body_all_with_meta) do + [ + { + 'Address' => '10.1.10.12', + 'Node' => 'foo', + 'Meta' => { + 'role' => 'redis', + 'az' => 'us-east-1a' + } + }, + { + 'Address' => '10.1.10.13', + 'Node' => 'bar', + 'Meta' => { + 'role' => 'redis', + 'az' => 'us-east-1a' + } + } + ] + end let(:body) do { 'Node' => { @@ -83,6 +103,7 @@ } end let(:all_with_dc_url) { '/v1/catalog/nodes?dc=dc1' } + let(:all_with_meta_url) { '/v1/catalog/nodes?node-meta=az:us-east-1a&node-meta=role:redis' } let(:body_all_with_dc) do [ { @@ -112,6 +133,25 @@ end end + describe 'GET ALL WITH METADATA' do + it 'lists all the nodes with specified metadata' do + json = JSON.generate(body_all_with_meta) + + faraday.stub(:get).with(all_with_meta_url).and_return(OpenStruct.new(body: json)) + + node = Diplomat::Node.new(faraday) + expect(node.get_all(meta: { az: 'us-east-1a', role: 'redis' }).size).to eq(2) + end + + it 'lists all the nodes' do + json = JSON.generate(body_all_with_dc) + + faraday.stub(:get).with(all_with_dc_url).and_return(OpenStruct.new(body: json)) + + node = Diplomat::Node.new(faraday) + expect(node.get_all(dc: 'dc1').size).to eq(1) + end + end describe 'GET' do let(:cn) do json = JSON.generate(body) diff --git a/spec/service_spec.rb b/spec/service_spec.rb index 52ee33d..5ed59ed 100644 --- a/spec/service_spec.rb +++ b/spec/service_spec.rb @@ -12,11 +12,16 @@ let(:key_url_with_datacenteroption) { "/v1/catalog/service/#{key}?dc=somedc" } let(:key_url_with_tagoption) { "/v1/catalog/service/#{key}?tag=sometag" } let(:services_url_with_datacenteroption) { '/v1/catalog/services?dc=somedc' } + let(:services_url_with_metadata_option) { '/v1/catalog/services?node-meta=lsb_release:16.04&node-meta=availability_zone:us-east-1a' } # rubocop:disable Metrics/LineLength let(:body) do [ { 'Node' => 'foo', 'Address' => '10.1.10.12', + 'NodeMeta' => { + 'lsb_release' => '16.04', + 'availability_zone' => 'us-east-1a' + }, 'ServiceID' => key, 'ServiceName' => key, 'ServiceTags' => ['sometag'], @@ -25,6 +30,10 @@ { 'Node' => 'bar', 'Address' => '10.1.10.13', + 'NodeMeta' => { + 'lsb_release' => '16.04', + 'availability_zone' => 'us-east-1a' + }, 'ServiceID' => key, 'ServiceName' => key, 'ServiceTags' => %w[sometag anothertag], @@ -202,6 +211,18 @@ expect(service.get_all(options).service1.first).to eq(service_one_tag) expect(service.get_all(options).service2.first).to eq(service_two_tag) end + it 'lists all the services for the specified metadata' do + json = JSON.generate(body_all) + + faraday.stub(:get).with(services_url_with_metadata_option).and_return(OpenStruct.new(body: json)) + + options = { meta: { lsb_release: '16.04', availability_zone: 'us-east-1a' } } + service = Diplomat::Service.new(faraday) + expect(service.get_all(options).service1).to be_an(Array) + expect(service.get_all(options).service2).to be_an(Array) + expect(service.get_all(options).service1.first).to eq(service_one_tag) + expect(service.get_all(options).service2.first).to eq(service_two_tag) + end end describe 'Register service' do