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 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