consul-templaterb 1.17.4 → 1.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +1 -0
- data/TemplateAPI.md +13 -0
- data/lib/consul/async/consul_template.rb +26 -1
- data/lib/consul/async/consul_template_engine.rb +1 -0
- data/lib/consul/async/json_endpoint.rb +215 -0
- data/lib/consul/async/version.rb +1 -1
- data/samples/list_ruby_versions_from_rubygems.txt.erb +7 -0
- data/samples/prometheus_consul_coordinates.erb +1 -2
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11600fd1fbd0d94c1cc32e9fdcc5734f5f8caa1a543adaa876b5831f4e0a8303
|
4
|
+
data.tar.gz: 69ce9c611cf41505b43d743adfca4f453857cea9f1df8a4e907a98c694468ed0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89ef2d0efa08adc140735b8df89ed45b5074e9a2ef90ccdbf96eb94f5cfb8a81d4ff807859f137263b1dcc1ae09458819e036da4fe08a557ef27ee76d106dfdc
|
7
|
+
data.tar.gz: 1668c8e3814b857af84f3f0cdd6011c3cb93733c4c62ae43424f268cd4bc9b0c9958c0d5568fc3466b47b84044619093b5ff58a136de4041e901953d8c944444
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,14 @@
|
|
2
2
|
|
3
3
|
## (UNRELEASED)
|
4
4
|
|
5
|
+
## 1.18.0 (July 27, 2019)
|
6
|
+
|
7
|
+
NEW FEATURES:
|
8
|
+
|
9
|
+
* Support for `remote_resource` provided by @kamaradclimber
|
10
|
+
* Added support for `remote_resource.as_json` to fetch JSON remote resource from a web server
|
11
|
+
* Added `samples/list_ruby_versions_from_rubygems.txt.erb` to demonstrate usage
|
12
|
+
|
5
13
|
## 1.17.3 (July 18, 2019)
|
6
14
|
|
7
15
|
BUGFIX:
|
data/README.md
CHANGED
@@ -256,6 +256,7 @@ examples:
|
|
256
256
|
datacenters and nodes and export it to prometheus easily to trigger alerts.
|
257
257
|
10. [List all services/Nodes with their statuses for all datacenters](samples/all_services.txt.erb)
|
258
258
|
11. [Show all services/instances not passing on all DCs](samples/tools/find_all_failing_services.txt.erb)
|
259
|
+
12. [List all rubygems consul versions from remote server JSON](samples/list_ruby_versions_from_rubygems.txt.erb)
|
259
260
|
|
260
261
|
If you want to test it quickly, you might try with (assuming your consul agent is listening on
|
261
262
|
`http://localhost:8500`):
|
data/TemplateAPI.md
CHANGED
@@ -434,6 +434,19 @@ secret('secret/foo', [force_ttl: intInSecond])
|
|
434
434
|
</div>
|
435
435
|
</details>
|
436
436
|
|
437
|
+
## remote_resource
|
438
|
+
|
439
|
+
### as_json(url, default_value, [refresh_delay_secs: intInSecond])
|
440
|
+
|
441
|
+
Fetch json data from any url. This allows to create templates with consul/vault data mixed in with data coming from other services/api.
|
442
|
+
Polling interval can be controlled with refresh_delay_secs.
|
443
|
+
|
444
|
+
```erb
|
445
|
+
remote_resource.as_json('http://my-api.dev/fridge/list.json', [])
|
446
|
+
```
|
447
|
+
|
448
|
+
Full example: [samples/list_ruby_versions_from_rubygems.txt.erb](samples/list_ruby_versions_from_rubygems.txt.erb)
|
449
|
+
|
437
450
|
## template_info()
|
438
451
|
|
439
452
|
It returns information about current template being rendered.
|
@@ -21,6 +21,22 @@ module Consul
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
class RemoteResource
|
25
|
+
def initialize(endpoints_manager)
|
26
|
+
@endp_manager = endpoints_manager
|
27
|
+
end
|
28
|
+
|
29
|
+
def as_json(url, default_value, refresh_delay_secs: 10)
|
30
|
+
conf = JSONConfiguration.new(url: url, min_duration: refresh_delay_secs, retry_on_non_diff: refresh_delay_secs)
|
31
|
+
ret = if default_value.is_a?(Array)
|
32
|
+
ConsulTemplateJSONArray.new(JSONEndpoint.new(conf, url, default_value))
|
33
|
+
else
|
34
|
+
ConsulTemplateJSONObject.new(JSONEndpoint.new(conf, url, default_value))
|
35
|
+
end
|
36
|
+
@endp_manager.create_if_missing(url, {}) { ret }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
24
40
|
# Encapsulation of endpoints to get coordinates
|
25
41
|
class Coordinate
|
26
42
|
def initialize(endpoints_manager)
|
@@ -62,7 +78,7 @@ module Consul
|
|
62
78
|
end
|
63
79
|
|
64
80
|
class EndPointsManager
|
65
|
-
attr_reader :consul_conf, :vault_conf, :net_info, :start_time, :coordinate
|
81
|
+
attr_reader :consul_conf, :vault_conf, :net_info, :start_time, :coordinate, :remote_resource
|
66
82
|
def initialize(consul_configuration, vault_configuration, trim_mode = nil)
|
67
83
|
@consul_conf = consul_configuration
|
68
84
|
@vault_conf = vault_configuration
|
@@ -89,6 +105,7 @@ module Consul
|
|
89
105
|
params: {}
|
90
106
|
}
|
91
107
|
@coordinate = Coordinate.new(self)
|
108
|
+
@remote_resource = RemoteResource.new(self)
|
92
109
|
|
93
110
|
# Setup token renewal
|
94
111
|
vault_setup_token_renew unless @vault_conf.token.nil? || !@vault_conf.token_renew
|
@@ -380,6 +397,11 @@ module Consul
|
|
380
397
|
end
|
381
398
|
end
|
382
399
|
|
400
|
+
# technically this class could be also an array, a simple string or any simple json object other than a hash.
|
401
|
+
class ConsulTemplateAbstractJSONObject < ConsulTemplateAbstractMap; end
|
402
|
+
|
403
|
+
class ConsulTemplateAbstractJSONArray < ConsulTemplateAbstractArray; end
|
404
|
+
|
383
405
|
class ServiceInstance < Hash
|
384
406
|
def initialize(obj)
|
385
407
|
merge!(obj)
|
@@ -478,6 +500,9 @@ module Consul
|
|
478
500
|
end
|
479
501
|
end
|
480
502
|
|
503
|
+
class ConsulTemplateJSONObject < ConsulTemplateAbstractJSONObject; end
|
504
|
+
class ConsulTemplateJSONArray < ConsulTemplateAbstractJSONArray; end
|
505
|
+
|
481
506
|
class ConsulAgentSelf < ConsulTemplateAbstractMap
|
482
507
|
def initialize(consul_endpoint)
|
483
508
|
super(consul_endpoint)
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'consul/async/utilities'
|
2
|
+
require 'consul/async/stats'
|
3
|
+
require 'em-http'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Consul
|
7
|
+
module Async
|
8
|
+
class JSONConfiguration
|
9
|
+
attr_reader :url, :retry_duration, :min_duration, :retry_on_non_diff,
|
10
|
+
:debug, :enable_gzip_compression
|
11
|
+
def initialize(url:,
|
12
|
+
debug: { network: false },
|
13
|
+
retry_duration: 10,
|
14
|
+
min_duration: 10,
|
15
|
+
retry_on_non_diff: 10,
|
16
|
+
enable_gzip_compression: true)
|
17
|
+
@url = url
|
18
|
+
@debug = debug
|
19
|
+
@enable_gzip_compression = enable_gzip_compression
|
20
|
+
@retry_duration = retry_duration
|
21
|
+
@min_duration = min_duration
|
22
|
+
@retry_on_non_diff = retry_on_non_diff
|
23
|
+
end
|
24
|
+
|
25
|
+
def create(_url)
|
26
|
+
# here we assume we don't need to cache configuration
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
class JSONResult
|
31
|
+
attr_reader :data, :http, :last_update, :stats, :retry_in
|
32
|
+
def initialize(data, modified, http, stats, retry_in, fake: false)
|
33
|
+
@data = data
|
34
|
+
@modified = modified
|
35
|
+
@http = http
|
36
|
+
@last_update = Time.now.utc
|
37
|
+
@stats = stats
|
38
|
+
@retry_in = retry_in
|
39
|
+
@fake = fake
|
40
|
+
end
|
41
|
+
|
42
|
+
def fake?
|
43
|
+
@fake
|
44
|
+
end
|
45
|
+
|
46
|
+
def modified?
|
47
|
+
@modified
|
48
|
+
end
|
49
|
+
|
50
|
+
def mutate(new_data)
|
51
|
+
@data = new_data.dup
|
52
|
+
@json = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def json
|
56
|
+
@json ||= JSON.parse(data)
|
57
|
+
end
|
58
|
+
|
59
|
+
def next_retry_at
|
60
|
+
next_retry + last_update
|
61
|
+
end
|
62
|
+
end
|
63
|
+
class HttpResponse
|
64
|
+
attr_reader :response_header, :response, :error
|
65
|
+
def initialize(http, override_nil_response = nil)
|
66
|
+
if http.nil?
|
67
|
+
@response_header = nil
|
68
|
+
@response = override_nil_response
|
69
|
+
@error = 'Not initialized yet'
|
70
|
+
else
|
71
|
+
@response_header = http.response_header.nil? ? nil : http.response_header.dup.freeze
|
72
|
+
@response = http.response.nil? || http.response.empty? ? override_nil_response : http.response.dup.freeze
|
73
|
+
@error = http.error.nil? ? nil : http.error.dup.freeze
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
class JSONEndpoint
|
78
|
+
attr_reader :conf, :url, :queue, :stats, :last_result, :enforce_json_200, :start_time, :default_value, :query_params
|
79
|
+
def initialize(conf, url, default_value, enforce_json_200 = true, query_params = {})
|
80
|
+
@conf = conf.create(url)
|
81
|
+
@default_value = default_value
|
82
|
+
@url = url
|
83
|
+
@queue = EM::Queue.new
|
84
|
+
@s_callbacks = []
|
85
|
+
@e_callbacks = []
|
86
|
+
@enforce_json_200 = enforce_json_200
|
87
|
+
@start_time = Time.now.utc
|
88
|
+
@consecutive_errors = 0
|
89
|
+
@query_params = query_params
|
90
|
+
@stopping = false
|
91
|
+
@stats = EndPointStats.new
|
92
|
+
@last_result = JSONResult.new(default_value.to_json, false, HttpResponse.new(nil), stats, 1, fake: true)
|
93
|
+
on_response { |result| @stats.on_response result }
|
94
|
+
on_error { |http| @stats.on_error http }
|
95
|
+
_enable_network_debug if conf.debug && conf.debug[:network]
|
96
|
+
fetch
|
97
|
+
queue << Object.new
|
98
|
+
end
|
99
|
+
|
100
|
+
def _enable_network_debug
|
101
|
+
on_response do |result|
|
102
|
+
stats = result.stats
|
103
|
+
warn "[DBUG][ OK ]#{result.modified? ? '[MODFIED]' : '[NO DIFF]'}" \
|
104
|
+
"[s:#{stats.successes},err:#{stats.errors}]" \
|
105
|
+
"[#{stats.body_bytes_human.ljust(8)}][#{stats.bytes_per_sec_human.ljust(9)}]"\
|
106
|
+
" #{url.ljust(48)}, next in #{result.retry_in} s"
|
107
|
+
end
|
108
|
+
on_error { |http| warn "[ERROR]: #{url}: #{http.error.inspect}" }
|
109
|
+
end
|
110
|
+
|
111
|
+
def on_response(&block)
|
112
|
+
@s_callbacks << block
|
113
|
+
end
|
114
|
+
|
115
|
+
def on_error(&block)
|
116
|
+
@e_callbacks << block
|
117
|
+
end
|
118
|
+
|
119
|
+
def ready?
|
120
|
+
@ready
|
121
|
+
end
|
122
|
+
|
123
|
+
def terminate
|
124
|
+
@stopping = true
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def build_request
|
130
|
+
res = {
|
131
|
+
head: {
|
132
|
+
'Accept' => 'application/json'
|
133
|
+
},
|
134
|
+
url: url,
|
135
|
+
keepalive: true,
|
136
|
+
callback: method(:on_response)
|
137
|
+
}
|
138
|
+
res[:head]['accept-encoding'] = 'identity' unless conf.enable_gzip_compression
|
139
|
+
@query_params.each_pair do |k, v|
|
140
|
+
res[:query][k] = v
|
141
|
+
end
|
142
|
+
res
|
143
|
+
end
|
144
|
+
|
145
|
+
def _compute_retry_in(retry_in)
|
146
|
+
retry_in / 2 + Consul::Async::Utilities.random.rand(retry_in)
|
147
|
+
end
|
148
|
+
|
149
|
+
def _handle_error(http)
|
150
|
+
retry_in = _compute_retry_in([600, conf.retry_duration + 2**@consecutive_errors].min)
|
151
|
+
::Consul::Async::Debug.puts_error "[#{url}] - #{http.error} - Retry in #{retry_in}s #{stats.body_bytes_human}"
|
152
|
+
@consecutive_errors += 1
|
153
|
+
http_result = HttpResponse.new(http)
|
154
|
+
EventMachine.add_timer(retry_in) do
|
155
|
+
yield
|
156
|
+
queue.push(Object.new)
|
157
|
+
end
|
158
|
+
@e_callbacks.each { |c| c.call(http_result) }
|
159
|
+
end
|
160
|
+
|
161
|
+
def fetch
|
162
|
+
options = {
|
163
|
+
connect_timeout: 5, # default connection setup timeout
|
164
|
+
inactivity_timeout: 60 # default connection inactivity (post-setup) timeout
|
165
|
+
}
|
166
|
+
connection = {
|
167
|
+
conn: EventMachine::HttpRequest.new(conf.url, options)
|
168
|
+
}
|
169
|
+
cb = proc do
|
170
|
+
http = connection[:conn].get(build_request)
|
171
|
+
http.callback do
|
172
|
+
if enforce_json_200 && http.response_header.status != 200 && http.response_header['Content-Type'] != 'application/json'
|
173
|
+
_handle_error(http) do
|
174
|
+
warn "[RETRY][#{url}] (#{@consecutive_errors} errors)" if (@consecutive_errors % 10) == 1
|
175
|
+
end
|
176
|
+
else
|
177
|
+
@consecutive_errors = 0
|
178
|
+
http_result = HttpResponse.new(http)
|
179
|
+
new_content = http_result.response.freeze
|
180
|
+
modified = @last_result.fake? || @last_result.data != new_content
|
181
|
+
retry_in = modified ? conf.min_duration : conf.retry_on_non_diff
|
182
|
+
retry_in = _compute_retry_in(retry_in)
|
183
|
+
retry_in = 0.1 if retry_in < 0.1
|
184
|
+
unless @stopping
|
185
|
+
EventMachine.add_timer(retry_in) do
|
186
|
+
queue.push(Object.new)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
result = JSONResult.new(new_content, modified, http_result, stats, retry_in, fake: false)
|
190
|
+
@last_result = result
|
191
|
+
@ready = true
|
192
|
+
@s_callbacks.each { |c| c.call(result) }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
http.errback do
|
197
|
+
unless @stopping
|
198
|
+
_handle_error(http) do
|
199
|
+
if (@consecutive_errors % 10) == 1
|
200
|
+
add_msg = http.error
|
201
|
+
if Gem.win_platform? && http.error.include?('unable to create new socket: Too many open files')
|
202
|
+
add_msg += "\n *** Windows does not support more than 2048 watches, watch less endpoints ***"
|
203
|
+
end
|
204
|
+
::Consul::Async::Debug.puts_error "[RETRY][#{url}] (#{@consecutive_errors} errors) due to #{add_msg}"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
queue.pop(&cb)
|
210
|
+
end
|
211
|
+
queue.pop(&cb)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
data/lib/consul/async/version.rb
CHANGED
@@ -0,0 +1,7 @@
|
|
1
|
+
All sorted consul-templaterb versions
|
2
|
+
<%
|
3
|
+
remote_resource
|
4
|
+
.as_json('https://rubygems.org/api/v1/versions/consul-templaterb.json', [], refresh_delay_secs: 1800)
|
5
|
+
.sort { |a, b| Gem::Version.create(a['number']) <=> Gem::Version.create(b['number']) }.each do |gem_version|
|
6
|
+
%> * Version: <%= gem_version['number'] %> (<%= gem_version['created_at'] %>) with <%= gem_version['downloads_count'] %> downloads
|
7
|
+
<% end %>
|
@@ -22,7 +22,6 @@ unless @consul_node_settings
|
|
22
22
|
if @consul_node_settings[:num_cpus] < 0
|
23
23
|
require 'etc'
|
24
24
|
@consul_node_settings[:num_cpus] = Etc.nprocessors - 1
|
25
|
-
STDERR.puts "Autodetected #{@consul_node_settings[:num_cpus]} CPUs"
|
26
25
|
end
|
27
26
|
end
|
28
27
|
|
@@ -135,4 +134,4 @@ all_nodes.each do |node, node_info|
|
|
135
134
|
<%
|
136
135
|
end
|
137
136
|
end
|
138
|
-
%>
|
137
|
+
%>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: consul-templaterb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.18.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- SRE Core Services
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-07-
|
11
|
+
date: 2019-07-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: em-http-request
|
@@ -183,6 +183,7 @@ files:
|
|
183
183
|
- lib/consul/async/consul_template_engine.rb
|
184
184
|
- lib/consul/async/consul_template_render.rb
|
185
185
|
- lib/consul/async/debug.rb
|
186
|
+
- lib/consul/async/json_endpoint.rb
|
186
187
|
- lib/consul/async/process_handler.rb
|
187
188
|
- lib/consul/async/stats.rb
|
188
189
|
- lib/consul/async/utilities.rb
|
@@ -224,6 +225,7 @@ files:
|
|
224
225
|
- samples/haproxy_dns.cfg.erb
|
225
226
|
- samples/keys.html.erb
|
226
227
|
- samples/kv_yaml_to_json.json.erb
|
228
|
+
- samples/list_ruby_versions_from_rubygems.txt.erb
|
227
229
|
- samples/metrics.erb
|
228
230
|
- samples/nodes.html.erb
|
229
231
|
- samples/prometheus_consul_coordinates.erb
|
@@ -257,7 +259,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
257
259
|
- !ruby/object:Gem::Version
|
258
260
|
version: '0'
|
259
261
|
requirements: []
|
260
|
-
|
262
|
+
rubyforge_project:
|
263
|
+
rubygems_version: 2.7.7
|
261
264
|
signing_key:
|
262
265
|
specification_version: 4
|
263
266
|
summary: Implementation of Consul template using Ruby and .erb templating language
|