consul-templaterb 1.17.4 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|