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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc1a1a088369364bcc3a1db3bbb1418312575858268189a15632be9b9fd59edf
4
- data.tar.gz: d0db07806bd2ba9bb597490cd34763c912d796385166a9e5a4b153e75cc8bd97
3
+ metadata.gz: 11600fd1fbd0d94c1cc32e9fdcc5734f5f8caa1a543adaa876b5831f4e0a8303
4
+ data.tar.gz: 69ce9c611cf41505b43d743adfca4f453857cea9f1df8a4e907a98c694468ed0
5
5
  SHA512:
6
- metadata.gz: cb8c4269065695d6fa08da55d7b60669b8c91d037bb2fd196a150872e02e67d24dd12b6a54390c27b28d59843703b64f82e7ea642d8a3c818c12d92abe695b1a
7
- data.tar.gz: 68be729f62bf5b5f6f9933ad660c3f9c2e6d44b17de1a17837952c6930af939ba70f0513c2a55bd73def8194506daebb77b40cf9f25f79ed9f510d817b37b468
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)
@@ -1,5 +1,6 @@
1
1
  require 'consul/async/utilities'
2
2
  require 'consul/async/consul_endpoint'
3
+ require 'consul/async/json_endpoint'
3
4
  require 'consul/async/vault_endpoint'
4
5
  require 'consul/async/consul_template'
5
6
  require 'consul/async/consul_template_render'
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Consul
2
2
  module Async
3
- VERSION = '1.17.4'.freeze
3
+ VERSION = '1.18.0'.freeze
4
4
  end
5
5
  end
@@ -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.17.4
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-18 00:00:00.000000000 Z
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
- rubygems_version: 3.0.4
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