diff --git a/lib/consulkit/client.rb b/lib/consulkit/client.rb index 06b05f0..265a01c 100644 --- a/lib/consulkit/client.rb +++ b/lib/consulkit/client.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'consulkit/configurable' +require 'consulkit/client/health' require 'consulkit/client/kv' require 'consulkit/client/session' @@ -9,6 +10,7 @@ module Consulkit class Client include Consulkit::Configurable + include Consulkit::Client::Health include Consulkit::Client::KV include Consulkit::Client::Session diff --git a/lib/consulkit/client/health.rb b/lib/consulkit/client/health.rb new file mode 100644 index 0000000..2b0d75f --- /dev/null +++ b/lib/consulkit/client/health.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Consulkit + class Client + # Methods for querying health checks registered with Consul. + module Health + + # Returns the list of service instances providing the given service, including health check + # information. + # + # @see https://developer.hashicorp.com/consul/api-docs/health#list-service-instances-for-service + # + # @param key [String] the key to read. + # @option query_params [Hash] optional query parameters. + # @option query_params [Boolean] :passing + # @option query_params [String] :filter + # + # @yield [Faraday::Response] The response from the underlying Faraday library. + # + # @return [Array] + def health_list_service_instances(service, query_params = {}) + response = get("/v1/health/service/#{service}", query_params) + + if block_given? + yield response + else + response.body + end + end + + end + end +end diff --git a/spec/cassettes/Consulkit_Client_Health/health_list_service_instances/lists_the_instances.yml b/spec/cassettes/Consulkit_Client_Health/health_list_service_instances/lists_the_instances.yml new file mode 100644 index 0000000..e104b08 --- /dev/null +++ b/spec/cassettes/Consulkit_Client_Health/health_list_service_instances/lists_the_instances.yml @@ -0,0 +1,119 @@ +--- +http_interactions: +- request: + method: get + uri: http://localhost:8500/v1/health/service/consul + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.9.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json + Vary: + - Accept-Encoding + X-Consul-Default-Acl-Policy: + - allow + X-Consul-Effective-Consistency: + - leader + X-Consul-Index: + - '14' + X-Consul-Knownleader: + - 'true' + X-Consul-Lastcontact: + - '0' + X-Consul-Query-Backend: + - blocking-query + Date: + - Thu, 06 Jun 2024 18:19:30 GMT + Content-Length: + - '2179' + body: + encoding: ASCII-8BIT + string: | + [ + { + "Node": { + "ID": "e0271afe-ff0c-d82f-8808-cbafde23b2bd", + "Node": "localhost", + "Address": "127.0.0.1", + "Datacenter": "dc1", + "TaggedAddresses": { + "lan": "127.0.0.1", + "lan_ipv4": "127.0.0.1", + "wan": "127.0.0.1", + "wan_ipv4": "127.0.0.1" + }, + "Meta": { + "consul-network-segment": "", + "consul-version": "1.16.4" + }, + "CreateIndex": 13, + "ModifyIndex": 14 + }, + "Service": { + "ID": "consul", + "Service": "consul", + "Tags": [], + "Address": "", + "Meta": { + "grpc_port": "8502", + "grpc_tls_port": "8503", + "non_voter": "false", + "raft_version": "3", + "read_replica": "false", + "serf_protocol_current": "2", + "serf_protocol_max": "5", + "serf_protocol_min": "1", + "version": "1.16.4" + }, + "Port": 8300, + "Weights": { + "Passing": 1, + "Warning": 1 + }, + "EnableTagOverride": false, + "Proxy": { + "Mode": "", + "MeshGateway": {}, + "Expose": {} + }, + "Connect": {}, + "PeerName": "", + "CreateIndex": 13, + "ModifyIndex": 13 + }, + "Checks": [ + { + "Node": "localhost", + "CheckID": "serfHealth", + "Name": "Serf Health Status", + "Status": "passing", + "Notes": "", + "Output": "Agent alive and reachable", + "ServiceID": "", + "ServiceName": "", + "ServiceTags": [], + "Type": "", + "Interval": "", + "Timeout": "", + "ExposedPort": 0, + "Definition": {}, + "CreateIndex": 13, + "ModifyIndex": 13 + } + ] + } + ] + recorded_at: Thu, 06 Jun 2024 18:19:30 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/cassettes/Consulkit_Client_Health/health_list_service_instances/passes_query_params.yml b/spec/cassettes/Consulkit_Client_Health/health_list_service_instances/passes_query_params.yml new file mode 100644 index 0000000..5f959ae --- /dev/null +++ b/spec/cassettes/Consulkit_Client_Health/health_list_service_instances/passes_query_params.yml @@ -0,0 +1,161 @@ +--- +http_interactions: +- request: + method: get + uri: http://localhost:8500/v1/health/service/consul?filter=Checks.Status%20==%20%22passing%22 + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.9.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json + Vary: + - Accept-Encoding + X-Consul-Default-Acl-Policy: + - allow + X-Consul-Effective-Consistency: + - leader + X-Consul-Index: + - '14' + X-Consul-Knownleader: + - 'true' + X-Consul-Lastcontact: + - '0' + X-Consul-Query-Backend: + - blocking-query + Date: + - Thu, 06 Jun 2024 18:19:30 GMT + Content-Length: + - '2179' + body: + encoding: ASCII-8BIT + string: | + [ + { + "Node": { + "ID": "e0271afe-ff0c-d82f-8808-cbafde23b2bd", + "Node": "localhost", + "Address": "127.0.0.1", + "Datacenter": "dc1", + "TaggedAddresses": { + "lan": "127.0.0.1", + "lan_ipv4": "127.0.0.1", + "wan": "127.0.0.1", + "wan_ipv4": "127.0.0.1" + }, + "Meta": { + "consul-network-segment": "", + "consul-version": "1.16.4" + }, + "CreateIndex": 13, + "ModifyIndex": 14 + }, + "Service": { + "ID": "consul", + "Service": "consul", + "Tags": [], + "Address": "", + "Meta": { + "grpc_port": "8502", + "grpc_tls_port": "8503", + "non_voter": "false", + "raft_version": "3", + "read_replica": "false", + "serf_protocol_current": "2", + "serf_protocol_max": "5", + "serf_protocol_min": "1", + "version": "1.16.4" + }, + "Port": 8300, + "Weights": { + "Passing": 1, + "Warning": 1 + }, + "EnableTagOverride": false, + "Proxy": { + "Mode": "", + "MeshGateway": {}, + "Expose": {} + }, + "Connect": {}, + "PeerName": "", + "CreateIndex": 13, + "ModifyIndex": 13 + }, + "Checks": [ + { + "Node": "localhost", + "CheckID": "serfHealth", + "Name": "Serf Health Status", + "Status": "passing", + "Notes": "", + "Output": "Agent alive and reachable", + "ServiceID": "", + "ServiceName": "", + "ServiceTags": [], + "Type": "", + "Interval": "", + "Timeout": "", + "ExposedPort": 0, + "Definition": {}, + "CreateIndex": 13, + "ModifyIndex": 13 + } + ] + } + ] + recorded_at: Thu, 06 Jun 2024 18:19:30 GMT +- request: + method: get + uri: http://localhost:8500/v1/health/service/consul?filter=Checks.Status%20!=%20%22passing%22 + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v2.9.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json + Vary: + - Accept-Encoding + X-Consul-Default-Acl-Policy: + - allow + X-Consul-Effective-Consistency: + - leader + X-Consul-Index: + - '14' + X-Consul-Knownleader: + - 'true' + X-Consul-Lastcontact: + - '0' + X-Consul-Query-Backend: + - blocking-query + Date: + - Thu, 06 Jun 2024 18:19:30 GMT + Content-Length: + - '3' + body: + encoding: UTF-8 + string: "[]\n" + recorded_at: Thu, 06 Jun 2024 18:19:30 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/consulkit/client/health_spec.rb b/spec/consulkit/client/health_spec.rb new file mode 100644 index 0000000..32a502a --- /dev/null +++ b/spec/consulkit/client/health_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +describe Consulkit::Client::Health, :vcr do + before do + @client = Consulkit.client + @service = 'consul' + end + + describe 'health_list_service_instances' do + it 'lists the instances' do + instances = @client.health_list_service_instances(@service) + + expect(instances.length).to be 1 + + first_instance = instances.first + + expect(first_instance.dig('Node', 'Node')).to eq('localhost') + expect(first_instance.dig('Service', 'ID')).to eq('consul') + expect(first_instance.dig('Checks', 0, 'CheckID')).to eq('serfHealth') + end + + it 'passes query params' do + instances = @client.health_list_service_instances(@service, filter: 'Checks.Status == "passing"') + + expect(instances.length).to be 1 + + first_instance = instances.first + + expect(first_instance.dig('Node', 'Node')).to eq('localhost') + expect(first_instance.dig('Service', 'ID')).to eq('consul') + expect(first_instance.dig('Checks', 0, 'CheckID')).to eq('serfHealth') + + instances = @client.health_list_service_instances(@service, filter: 'Checks.Status != "passing"') + + expect(instances.length).to be 0 + end + end +end