diplomat-blsk 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ module Diplomat
2
+ # Methods for interacting with the Consul ACL API endpoint
3
+ class Acl < Diplomat::RestClient
4
+ include ApiOptions
5
+
6
+ @access_methods = %i[list info create destroy update]
7
+ attr_reader :id, :type, :acl
8
+
9
+ # Get Acl info by ID
10
+ # @param id [String] ID of the Acl to get
11
+ # @return [Hash]
12
+ # rubocop:disable PerceivedComplexity, MethodLength, CyclomaticComplexity, AbcSize
13
+ def info(id, options = nil, not_found = :reject, found = :return)
14
+ @id = id
15
+ @options = options
16
+ url = ["/v1/acl/info/#{id}"]
17
+ url << check_acl_token
18
+ url << use_consistency(options)
19
+
20
+ raw = @conn_no_err.get concat_url url
21
+ if raw.status == 200 && raw.body.chomp != 'null'
22
+ case found
23
+ when :reject
24
+ raise Diplomat::AclAlreadyExists, id
25
+ when :return
26
+ @raw = raw
27
+ return parse_body
28
+ end
29
+ elsif raw.status == 200 && raw.body.chomp == 'null'
30
+ case not_found
31
+ when :reject
32
+ raise Diplomat::AclNotFound, id
33
+ when :return
34
+ return nil
35
+ end
36
+ else
37
+ raise Diplomat::UnknownStatus, "status #{raw.status}: #{raw.body}"
38
+ end
39
+ end
40
+ # rubocop:enable PerceivedComplexity, MethodLength, CyclomaticComplexity, AbcSize
41
+
42
+ # List all Acls
43
+ # @return [List] list of [Hash] of Acls
44
+ def list
45
+ url = ['/v1/acl/list']
46
+ url += check_acl_token
47
+ @raw = @conn_no_err.get concat_url url
48
+ parse_body
49
+ end
50
+
51
+ # Update an Acl definition, create if not present
52
+ # @param value [Hash] Acl definition, ID field is mandatory
53
+ # @return [Hash] The result Acl
54
+ def update(value)
55
+ raise Diplomat::IdParameterRequired unless value['ID']
56
+
57
+ @raw = @conn.put do |req|
58
+ url = ['/v1/acl/update']
59
+ url += check_acl_token
60
+ url += use_cas(@options)
61
+ req.url concat_url url
62
+ req.body = value.to_json
63
+ end
64
+ parse_body
65
+ end
66
+
67
+ # Create an Acl definition
68
+ # @param value [Hash] Acl definition, ID field is mandatory
69
+ # @return [Hash] The result Acl
70
+ def create(value)
71
+ @raw = @conn.put do |req|
72
+ url = ['/v1/acl/create']
73
+ url += check_acl_token
74
+ url += use_cas(@options)
75
+ req.url concat_url url
76
+ req.body = value.to_json
77
+ end
78
+ parse_body
79
+ end
80
+
81
+ # Destroy an ACl token by its id
82
+ # @param ID [String] the Acl ID
83
+ # @return [Bool]
84
+ def destroy(id)
85
+ @id = id
86
+ url = ["/v1/acl/destroy/#{@id}"]
87
+ url << check_acl_token
88
+ @raw = @conn.put concat_url url
89
+ @raw.body.chomp == 'true'
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,70 @@
1
+ require 'base64'
2
+ require 'faraday'
3
+
4
+ module Diplomat
5
+ # Agent API endpoint methods
6
+ # @see https://www.consul.io/docs/agent/http/agent.html
7
+ class Agent < Diplomat::RestClient
8
+ @access_methods = %i[self checks services members]
9
+
10
+ # Get agent configuration
11
+ # @return [OpenStruct] all data associated with the node
12
+ def self
13
+ url = ['/v1/agent/self']
14
+
15
+ # If the request fails, it's probably due to a bad path
16
+ # so return a PathNotFound error.
17
+ begin
18
+ ret = @conn.get concat_url url
19
+ rescue Faraday::ClientError
20
+ raise Diplomat::PathNotFound
21
+ end
22
+ JSON.parse(ret.body).tap { |node| OpenStruct.new node }
23
+ end
24
+
25
+ # Get local agent checks
26
+ # @return [OpenStruct] all agent checks
27
+ def checks
28
+ url = ['/v1/agent/checks']
29
+
30
+ # If the request fails, it's probably due to a bad path
31
+ # so return a PathNotFound error.
32
+ begin
33
+ ret = @conn.get concat_url url
34
+ rescue Faraday::ClientError
35
+ raise Diplomat::PathNotFound
36
+ end
37
+ JSON.parse(ret.body).tap { |node| OpenStruct.new node }
38
+ end
39
+
40
+ # Get local agent services
41
+ # @return [OpenStruct] all agent services
42
+ def services
43
+ url = ['/v1/agent/services']
44
+
45
+ # If the request fails, it's probably due to a bad path
46
+ # so return a PathNotFound error.
47
+ begin
48
+ ret = @conn.get concat_url url
49
+ rescue Faraday::ClientError
50
+ raise Diplomat::PathNotFound
51
+ end
52
+ JSON.parse(ret.body).tap { |node| OpenStruct.new node }
53
+ end
54
+
55
+ # Get cluster members (as seen by the agent)
56
+ # @return [OpenStruct] all members
57
+ def members
58
+ url = ['/v1/agent/members']
59
+
60
+ # If the request fails, it's probably due to a bad path
61
+ # so return a PathNotFound error.
62
+ begin
63
+ ret = @conn.get concat_url url
64
+ rescue Faraday::ClientError
65
+ raise Diplomat::PathNotFound
66
+ end
67
+ JSON.parse(ret.body).map { |node| OpenStruct.new node }
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,46 @@
1
+ module Diplomat
2
+ # Helper methods for interacting with the Consul RESTful API
3
+ module ApiOptions
4
+ def check_acl_token
5
+ use_named_parameter('token', Diplomat.configuration.acl_token)
6
+ end
7
+
8
+ def use_cas(options)
9
+ options ? use_named_parameter('cas', options[:cas]) : []
10
+ end
11
+
12
+ def use_consistency(options)
13
+ options && options[:consistency] ? [options[:consistency].to_s] : []
14
+ end
15
+
16
+ # Mapping for valid key/value store transaction verbs and required parameters
17
+ #
18
+ # @return [Hash] valid key/store transaction verbs and required parameters
19
+ # rubocop:disable MethodLength
20
+ def valid_transaction_verbs
21
+ {
22
+ 'set' => %w[Key Value],
23
+ 'cas' => %w[Key Value Index],
24
+ 'lock' => %w[Key Value Session],
25
+ 'unlock' => %w[Key Value Session],
26
+ 'get' => %w[Key],
27
+ 'get-tree' => %w[Key],
28
+ 'check-index' => %w[Key Index],
29
+ 'check-session' => %w[Key Session],
30
+ 'delete' => %w[Key],
31
+ 'delete-tree' => %w[Key],
32
+ 'delete-cas' => %w[Key Index]
33
+ }
34
+ end
35
+ # rubocop:enable MethodLength
36
+
37
+ # Key/value store transactions that require that a value be set
38
+ #
39
+ # @return [Array<String>] verbs that require a value be set
40
+ def valid_value_transactions
41
+ @valid_value_transactions ||= valid_transaction_verbs.select do |verb, requires|
42
+ verb if requires.include? 'Value'
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,81 @@
1
+ module Diplomat
2
+ # Methods for interacting with the Consul check API endpoint
3
+ class Check < Diplomat::RestClient
4
+ @access_methods = %i[checks register_script register_ttl
5
+ deregister pass warn fail]
6
+
7
+ # Get registered checks
8
+ # @return [OpenStruct] all data associated with the service
9
+ def checks
10
+ ret = @conn.get '/v1/agent/checks'
11
+ JSON.parse(ret.body)
12
+ end
13
+
14
+ # Register a check
15
+ # @param check_id [String] the unique id of the check
16
+ # @param name [String] the name
17
+ # @param notes [String] notes about the check
18
+ # @param script [String] command to be run for check
19
+ # @param interval [String] frequency (with units) of the check execution
20
+ # @param ttl [String] time (with units) to mark a check down
21
+ # @return [Integer] Status code
22
+ #
23
+ def register_script(check_id, name, notes, script, interval)
24
+ ret = @conn.put do |req|
25
+ req.url '/v1/agent/check/register'
26
+ req.body = JSON.generate(
27
+ 'ID' => check_id, 'Name' => name, 'Notes' => notes, 'Script' => script, 'Interval' => interval
28
+ )
29
+ end
30
+ ret.status == 200
31
+ end
32
+
33
+ # Register a TTL check
34
+ # @param check_id [String] the unique id of the check
35
+ # @param name [String] the name
36
+ # @param notes [String] notes about the check
37
+ # @param ttl [String] time (with units) to mark a check down
38
+ # @return [Boolean] Success
39
+ def register_ttl(check_id, name, notes, ttl)
40
+ ret = @conn.put do |req|
41
+ req.url '/v1/agent/check/register'
42
+ req.body = JSON.generate(
43
+ 'ID' => check_id, 'Name' => name, 'Notes' => notes, 'TTL' => ttl
44
+ )
45
+ end
46
+ ret.status == 200
47
+ end
48
+
49
+ # Deregister a check
50
+ # @param check_id [String] the unique id of the check
51
+ # @return [Integer] Status code
52
+ def deregister(check_id)
53
+ ret = @conn.get "/v1/agent/check/deregister/#{check_id}"
54
+ ret.status == 200
55
+ end
56
+
57
+ # Pass a check
58
+ # @param check_id [String] the unique id of the check
59
+ # @return [Integer] Status code
60
+ def pass(check_id)
61
+ ret = @conn.get "/v1/agent/check/pass/#{check_id}"
62
+ ret.status == 200
63
+ end
64
+
65
+ # Warn a check
66
+ # @param check_id [String] the unique id of the check
67
+ # @return [Integer] Status code
68
+ def warn(check_id)
69
+ ret = @conn.get "/v1/agent/check/warn/#{check_id}"
70
+ ret.status == 200
71
+ end
72
+
73
+ # Warn a check
74
+ # @param check_id [String] the unique id of the check
75
+ # @return [Integer] Status code
76
+ def fail(check_id)
77
+ ret = @conn.get "/v1/agent/check/fail/#{check_id}"
78
+ ret.status == 200
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,28 @@
1
+ module Diplomat
2
+ # Methods for configuring Diplomat
3
+ class Configuration
4
+ attr_reader :middleware
5
+ attr_accessor :url, :acl_token, :options
6
+
7
+ # Override defaults for configuration
8
+ # @param url [String] consul's connection URL
9
+ # @param acl_token [String] a connection token used when making requests to consul
10
+ # @param options [Hash] extra options to configure Faraday::Connection
11
+ def initialize(url = 'http://localhost:8500', acl_token = nil, options = {})
12
+ @middleware = []
13
+ @url = url
14
+ @acl_token = acl_token
15
+ @options = options
16
+ end
17
+
18
+ # Define a middleware for Faraday
19
+ # @param middleware [Class] Faraday Middleware class
20
+ def middleware=(middleware)
21
+ if middleware.is_a? Array
22
+ @middleware = middleware
23
+ return
24
+ end
25
+ @middleware = [middleware]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ module Diplomat
2
+ # Methods for interacting with the Consul dataceneter API endpoint
3
+ class Datacenter < Diplomat::RestClient
4
+ @access_methods = [:get]
5
+
6
+ # Get an array of all avaliable datacenters accessible by the local consul agent
7
+ # @param meta [Hash] output structure containing header information about the request (index)
8
+ # @return [OpenStruct] all datacenters avaliable to this consul agent
9
+ def get(meta = nil)
10
+ url = ['/v1/catalog/datacenters']
11
+
12
+ ret = @conn.get concat_url url
13
+
14
+ if meta && ret.headers
15
+ meta[:index] = ret.headers['x-consul-index']
16
+ meta[:knownleader] = ret.headers['x-consul-knownleader']
17
+ meta[:lastcontact] = ret.headers['x-consul-lastcontact']
18
+ end
19
+ JSON.parse(ret.body)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module Diplomat
2
+ class KeyNotFound < StandardError; end
3
+ class PathNotFound < StandardError; end
4
+ class KeyAlreadyExists < StandardError; end
5
+ class AclNotFound < StandardError; end
6
+ class AclAlreadyExists < StandardError; end
7
+ class EventNotFound < StandardError; end
8
+ class EventAlreadyExists < StandardError; end
9
+ class QueryNotFound < StandardError; end
10
+ class QueryAlreadyExists < StandardError; end
11
+ class UnknownStatus < StandardError; end
12
+ class IdParameterRequired < StandardError; end
13
+ class InvalidTransaction < StandardError; end
14
+ end
@@ -0,0 +1,177 @@
1
+ module Diplomat
2
+ # Methods for interacting with the Consul event API endpoint
3
+ class Event < Diplomat::RestClient
4
+ include ApiOptions
5
+
6
+ @access_methods = %i[fire get_all get]
7
+
8
+ # Send an event
9
+ # @param name [String] the event name
10
+ # @param value [String] the payload of the event
11
+ # @param service [String] the target service name
12
+ # @param node [String] the target node name
13
+ # @param tag [String] the target tag name, must only be used with service
14
+ # @param dc [String] the dc to target
15
+ # @return [nil]
16
+ # rubocop:disable Metrics/ParameterLists
17
+ def fire(name, value = nil, service = nil, node = nil, tag = nil, dc = nil)
18
+ url = ["/v1/event/fire/#{name}"]
19
+ url += check_acl_token
20
+ url += use_named_parameter('service', service) if service
21
+ url += use_named_parameter('node', node) if node
22
+ url += use_named_parameter('tag', tag) if tag
23
+ url += use_named_parameter('dc', dc) if dc
24
+
25
+ @conn.put concat_url(url), value
26
+ nil
27
+ end
28
+ # rubocop:enable Metrics/ParameterLists
29
+
30
+ # Get the list of events matching name
31
+ # @param name [String] the name of the event (regex)
32
+ # @param not_found [Symbol] behaviour if there are no events matching name;
33
+ # :reject with exception, :return degenerate value, or :wait for a non-empty list
34
+ # @param found [Symbol] behaviour if there are already events matching name;
35
+ # :reject with exception, :return its current value, or :wait for its next value
36
+ # @return [Array[hash]] The list of { :name, :payload } hashes
37
+ # @note
38
+ # Events are sent via the gossip protocol; there is no guarantee of delivery
39
+ # success or order, but the local agent will store up to 256 events that do
40
+ # arrive. This method lists those events.
41
+ # It has the same semantics as Kv::get, except the value returned is a list
42
+ # i.e. the current value is all events up until now, the next value is the
43
+ # current list plus the next event to arrive.
44
+ # To get a specific event in the sequence, @see #get
45
+ # When trying to get a list of events matching a name, there are two possibilities:
46
+ # - The list doesn't (yet) exist / is empty
47
+ # - The list exists / is non-empty
48
+ # The combination of not_found and found behaviour gives maximum possible
49
+ # flexibility. For X: reject, R: return, W: wait
50
+ # - X X - meaningless; never return a value
51
+ # - X R - "normal" non-blocking get operation. Default
52
+ # - X W - get the next value only (must have a current value)
53
+ # - R X - meaningless; never return a meaningful value
54
+ # - R R - "safe" non-blocking, non-throwing get-or-default operation
55
+ # - R W - get the next value or a default
56
+ # - W X - get the first value only (must not have a current value)
57
+ # - W R - get the first or current value; always return something, but
58
+ # block only when necessary
59
+ # - W W - get the first or next value; wait until there is an update
60
+ # rubocop:disable MethodLength, AbcSize
61
+ def get_all(name = nil, not_found = :reject, found = :return)
62
+ url = ['/v1/event/list']
63
+ url += check_acl_token
64
+ url += use_named_parameter('name', name)
65
+ url = concat_url url
66
+
67
+ # Event list never returns 404 or blocks, but may return an empty list
68
+ @raw = @conn.get url
69
+ if JSON.parse(@raw.body).count.zero?
70
+ case not_found
71
+ when :reject
72
+ raise Diplomat::EventNotFound, name
73
+ when :return
74
+ return []
75
+ end
76
+ else
77
+ case found
78
+ when :reject
79
+ raise Diplomat::EventAlreadyExists, name
80
+ when :return
81
+ # Always set the response to 200 so we always return
82
+ # the response body.
83
+ @raw.status = 200
84
+ @raw = parse_body
85
+ return return_payload
86
+ end
87
+ end
88
+
89
+ @raw = wait_for_next_event(url)
90
+ @raw = parse_body
91
+ return_payload
92
+ end
93
+ # rubocop:enable MethodLength, AbcSize
94
+
95
+ # Get a specific event in the sequence matching name
96
+ # @param name [String] the name of the event (regex)
97
+ # @param token [String|Symbol] the ordinate of the event in the sequence;
98
+ # String are tokens returned by previous calls to this function
99
+ # Symbols are the special tokens :first, :last, and :next
100
+ # @param not_found [Symbol] behaviour if there is no matching event;
101
+ # :reject with exception, :return degenerate value, or :wait for event
102
+ # @param found [Symbol] behaviour if there is a matching event;
103
+ # :reject with exception, or :return its current value
104
+ # @return [hash] A hash with keys :value and :token;
105
+ # :value is a further hash of the :name and :payload of the event,
106
+ # :token is the event's ordinate in the sequence and can be passed to future calls to get the subsequent event
107
+ # @note
108
+ # Whereas the consul API for events returns all past events that match
109
+ # name, this method allows retrieval of individual events from that
110
+ # sequence. However, because consul's API isn't conducive to this, we can
111
+ # offer first, last, next (last + 1) events, or arbitrary events in the
112
+ # middle, though these can only be identified relative to the preceding
113
+ # event. However, this is ideal for iterating through the sequence of
114
+ # events (while being sure that none are missed).
115
+ # rubocop:disable MethodLength, CyclomaticComplexity, AbcSize
116
+ def get(name = nil, token = :last, not_found = :wait, found = :return)
117
+ url = ['/v1/event/list']
118
+ url += check_acl_token
119
+ url += use_named_parameter('name', name)
120
+ @raw = @conn.get concat_url url
121
+ body = JSON.parse(@raw.body)
122
+ # TODO: deal with unknown symbols, invalid indices (find_index will return nil)
123
+ idx = case token
124
+ when :first then 0
125
+ when :last then body.length - 1
126
+ when :next then body.length
127
+ else body.find_index { |e| e['ID'] == token } + 1
128
+ end
129
+ if idx == body.length
130
+ case not_found
131
+ when :reject
132
+ raise Diplomat::EventNotFound, name
133
+ when :return
134
+ event_name = ''
135
+ event_payload = ''
136
+ event_token = :last
137
+ when :wait
138
+ @raw = wait_for_next_event(url)
139
+ @raw = parse_body
140
+ # If it's possible for two events to arrive at once,
141
+ # this needs to #find again:
142
+ event = @raw.last
143
+ event_name = event['Name']
144
+ event_payload = Base64.decode64(event['Payload'])
145
+ event_token = event['ID']
146
+ end
147
+ else
148
+ case found
149
+ when :reject
150
+ raise Diplomat::EventAlreadyExits, name
151
+ when :return
152
+ event = body[idx]
153
+ event_name = event['Name']
154
+ event_payload = Base64.decode64(event['Payload'])
155
+ event_token = event['ID']
156
+ end
157
+ end
158
+
159
+ {
160
+ value: { name: event_name, payload: event_payload },
161
+ token: event_token
162
+ }
163
+ end
164
+ # rubocop:enable MethodLength, CyclomaticComplexity, AbcSize
165
+
166
+ private
167
+
168
+ def wait_for_next_event(url)
169
+ index = @raw.headers['x-consul-index']
170
+ url = [url, use_named_parameter('index', index)].join('&')
171
+ @conn.get do |req|
172
+ req.url concat_url url
173
+ req.options.timeout = 86_400
174
+ end
175
+ end
176
+ end
177
+ end