consul-templaterb 1.23.0 → 1.24.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: 1322b0762c3a9ea41ae648227b38f42e21166193fbae07cc5b1b450b7814ce6b
4
- data.tar.gz: e656d9e1b4cff345704bfe1e32ce71fc9be6b9433f554a0c6184ef506423e453
3
+ metadata.gz: f4f6bc7af6e5c8ccdb5417157b443de3c43ae33ffbcd90953b9960033d1cf960
4
+ data.tar.gz: f9635b5936d0f11cbfa32b7088faba4d90e1b43ed4ac7c4f4127f6d450981435
5
5
  SHA512:
6
- metadata.gz: 89029f97088f851c676263d79d584e0b6cfa22b72f2008690857f5341b7af3f8b7b627d082ada09b057010c97358cd75846f18ed8b825e4e212a0c07c0998abf
7
- data.tar.gz: e2581df1a03190bb75a1f40838282ab49c9184e32a5fcf7807b1f86febbb1b0a88ea1deb93cddfe256c3aeccfeaf3874d4ad99f8b53935200fd15c9c8d71d821
6
+ metadata.gz: 68aae80142edd53811fe41b427573f6dd06e5dfdd19bba59607050927a7f17aa4ff1cb70f697e51213105fd97771fccafef833592d3d2a58f6857a5d8f982231
7
+ data.tar.gz: 3a74bf0df8f28e3578615db1e2155772668528814c14ae6010849cc7cbdccc2c662485f9fead52b5849993407cf708d97902c9cf4ae2ff1ce1345bff3f769281
@@ -3,6 +3,9 @@ AllCops:
3
3
  - bundle/**/*
4
4
  TargetRubyVersion: 2.4
5
5
 
6
+ Layout/LineLength:
7
+ Max: 175
8
+
6
9
  Metrics/AbcSize:
7
10
  Max: 82
8
11
 
@@ -18,9 +21,6 @@ Metrics/ClassLength:
18
21
  Metrics/CyclomaticComplexity:
19
22
  Max: 20
20
23
 
21
- Metrics/LineLength:
22
- Max: 175
23
-
24
24
  Metrics/MethodLength:
25
25
  Max: 65
26
26
 
@@ -30,14 +30,28 @@ Metrics/ParameterLists:
30
30
  Metrics/PerceivedComplexity:
31
31
  Max: 23
32
32
 
33
- Style/Documentation:
33
+ # We use `dc` as a parameter in many methods
34
+ Naming/MethodParameterName:
35
+ Enabled: false
36
+
37
+ Naming/VariableNumber:
34
38
  Enabled: false
35
39
 
40
+ Style/Documentation:
41
+ Enabled: true
42
+
36
43
  Style/FrozenStringLiteralComment:
37
44
  Enabled: false
38
45
 
46
+ Style/HashEachMethods:
47
+ Enabled: true
48
+
49
+ Style/HashTransformKeys:
50
+ Enabled: true
51
+
52
+ Style/HashTransformValues:
53
+ Enabled: true
54
+
39
55
  Style/MultilineBlockChain:
40
56
  Enabled: false
41
57
 
42
- Style/VariableNumber:
43
- Enabled: false
@@ -1,6 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.4.8
3
+ - 2.4.9
4
4
  - 2.5.7
5
5
  - 2.6.5
6
6
  - 2.7.0
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## (UNRELEASED)
4
4
 
5
+ ## 1.24.0 (February 19, 2020)
6
+
7
+ NEW FEATURES:
8
+
9
+ * pluggable node meta
10
+
5
11
  ## 1.23.0 (February 18, 2020)
6
12
 
7
13
  NEW FEATURES:
@@ -14,12 +14,13 @@ end
14
14
  def compute_default_output(source)
15
15
  dest = source.gsub(/\.erb$/, '')
16
16
  raise "Source and destination cannot be the same in #{source}" if source == dest || dest.empty?
17
+
17
18
  dest
18
19
  end
19
20
 
20
21
  options = {
21
22
  erb: {
22
- trim_mode: '-', # trim_mode for ERB
23
+ trim_mode: '-' # trim_mode for ERB
23
24
  },
24
25
  vault: {
25
26
  debug: {
@@ -59,22 +60,22 @@ options = {
59
60
  enable_gzip_compression: true,
60
61
  paths: {
61
62
  '/v1/catalog/services': {
62
- min_duration: 15, # Since services change a lot, refresh services every 15 seconds
63
+ min_duration: 15 # Since services change a lot, refresh services every 15 seconds
63
64
  },
64
65
  '/v1/catalog/nodes': {
65
- min_duration: 15, # Do not wake up before 15 seconds when node appear/disappear
66
+ min_duration: 15 # Do not wake up before 15 seconds when node appear/disappear
66
67
  },
67
68
  '/v1/coordinate/nodes': {
68
- min_duration: 60, # Since coordinates change a lot, refresh coordinates every 30 seconds
69
+ min_duration: 60 # Since coordinates change a lot, refresh coordinates every 30 seconds
69
70
  },
70
71
  '/v1/catalog/datacenters': {
71
- min_duration: 60, # Datacenters are not added every minute, right?
72
+ min_duration: 60 # Datacenters are not added every minute, right?
72
73
  },
73
74
  '/v1/agent/metrics': {
74
- min_duration: 60, # Refresh metrics only minute max
75
+ min_duration: 60 # Refresh metrics only minute max
75
76
  },
76
77
  '/v1/agent/self': {
77
- min_duration: 60, # Refresh self info every minute max
78
+ min_duration: 60 # Refresh self info every minute max
78
79
  }
79
80
  }
80
81
  }
@@ -89,18 +90,19 @@ optparse = OptionParser.new do |opts|
89
90
  opts.banner = usage_text
90
91
 
91
92
  opts.on('-h', '--help', 'Show help') do
92
- STDERR.puts opts
93
+ warn opts
93
94
  exit 0
94
95
  end
95
96
 
96
97
  opts.on('-v', '--version', 'Show Version') do
97
- STDERR.puts Consul::Async::VERSION
98
+ warn Consul::Async::VERSION
98
99
  exit 0
99
100
  end
100
101
 
101
102
  opts.on('--retry [RETRIES]', '--consul-retry-attempts [RETRIES]', Integer,
102
103
  "If consul fails after n retries, stop the program, default=#{options[:consul][:max_consecutive_errors_on_endpoint]}") do |value|
103
104
  raise "#{value} Must be greater or equal to 0" unless value >= 0
105
+
104
106
  options[:consul][:max_consecutive_errors_on_endpoint] = value
105
107
  end
106
108
 
@@ -141,6 +143,7 @@ optparse = OptionParser.new do |opts|
141
143
  opts.on('--vault-retry [RETRIES]', '--vault-retry-attempts [RETRIES]', Integer,
142
144
  "If vault fails after n retries, stop the program, default=#{options[:vault][:max_consecutive_errors_on_endpoint]}") do |value|
143
145
  raise "#{value} Must be greater or equal to 0" unless value >= 0
146
+
144
147
  options[:vault][:max_consecutive_errors_on_endpoint] = value
145
148
  end
146
149
 
@@ -150,6 +153,7 @@ optparse = OptionParser.new do |opts|
150
153
 
151
154
  opts.on('-w', '--wait=<min_duration>', Integer, 'Wait at least n seconds before each template generation') do |min_duration|
152
155
  raise "--wait=#{min_duration} must be greater than 0" unless min_duration.positive?
156
+
153
157
  consul_engine.template_frequency = min_duration
154
158
  end
155
159
 
@@ -169,6 +173,7 @@ optparse = OptionParser.new do |opts|
169
173
  raise "Please specify a signal, use any of: #{valid_signals.inspect}" unless val
170
174
  return nil if val == none_value
171
175
  raise "Invalid signal #{val}, valid signals: #{valid_signals.inspect}" unless valid_signals.include? val
176
+
172
177
  val
173
178
  end
174
179
 
@@ -198,7 +203,7 @@ optparse = OptionParser.new do |opts|
198
203
  if all_ready
199
204
  modified = results.any?(&:modified)
200
205
  if @programs[cmd].nil?
201
- STDERR.puts "[EXEC] Starting process: #{cmd}... on_reload=#{sig_reload ? sig_reload : 'NONE'} on_term=#{sig_term}"
206
+ warn "[EXEC] Starting process: #{cmd}... on_reload=#{sig_reload || 'NONE'} on_term=#{sig_term}"
202
207
  @programs[cmd] = Consul::Async::ProcessHandler.new(cmd, sig_reload: sig_reload, sig_term: sig_term)
203
208
  @programs[cmd].start
204
209
  else
@@ -207,7 +212,7 @@ optparse = OptionParser.new do |opts|
207
212
  begin
208
213
  @programs[cmd].process_status
209
214
  rescue Consul::Async::ProcessDoesNotExist => e
210
- STDERR.puts "[FATAL] The process is dead, aborting run: #{e.inspect}"
215
+ warn "[FATAL] The process is dead, aborting run: #{e.inspect}"
211
216
  template_manager.terminate
212
217
  EventMachine.stop
213
218
  end
@@ -243,9 +248,10 @@ optparse = OptionParser.new do |opts|
243
248
  dest = splitted[1]
244
249
  unless dest
245
250
  dest = compute_default_output(source)
246
- STDERR.puts "-t --template #{tpl} : Since output has not been set, using #{dest}"
251
+ warn "-t --template #{tpl} : Since output has not been set, using #{dest}"
247
252
  end
248
253
  raise "Source and destination cannot be the same in #{tpl}" if source == dest || dest.empty?
254
+
249
255
  command = splitted[2]
250
256
  parameter_file = splitted[3]
251
257
  params = {}
@@ -255,6 +261,7 @@ optparse = OptionParser.new do |opts|
255
261
  consul_engine.add_template_callback do |_all_ready, _template_manager, results|
256
262
  results.each do |res|
257
263
  next unless res.ready? && res.modified && res.output_file == dest && res.template_file == source
264
+
258
265
  # Our template has been fully rendered
259
266
  system(command)
260
267
  end
@@ -265,7 +272,7 @@ optparse = OptionParser.new do |opts|
265
272
  opts.on('-o', '--once', 'Do not run the process as a daemon') do
266
273
  consul_engine.add_template_callback do |all_ready, template_manager, _|
267
274
  if all_ready
268
- STDERR.puts '[INFO] Program ends since daemon mode has been disabled, file(s) has been written'
275
+ warn '[INFO] Program ends since daemon mode has been disabled, file(s) has been written'
269
276
  template_manager.terminate
270
277
  EventMachine.stop
271
278
  end
@@ -275,7 +282,7 @@ end
275
282
 
276
283
  def kill_program
277
284
  @programs.each do |k, v|
278
- STDERR.puts "Killing process #{k}..."
285
+ warn "Killing process #{k}..."
279
286
  v.kill
280
287
  end
281
288
  @programs = {}
@@ -295,14 +302,12 @@ def find_max_descriptors(max_descripors)
295
302
  end
296
303
 
297
304
  %i[consul vault].each do |type|
298
- unless options[type][:base_url].start_with? 'http', 'https'
299
- options[type][:base_url] = "http://#{options[type][:base_url]}"
300
- end
305
+ options[type][:base_url] = "http://#{options[type][:base_url]}" unless options[type][:base_url].start_with? 'http', 'https'
301
306
  end
302
307
 
303
308
  # Since we might be using a lots of descriptors, document this
304
309
  new_size = find_max_descriptors(65_536)
305
- STDERR.puts "Max number of descriptors set to #{new_size}" if options[:consul][:debug][:network]
310
+ warn "Max number of descriptors set to #{new_size}" if options[:consul][:debug][:network]
306
311
 
307
312
  # This is needed to avoid EM not to crash on some Linux Hosts
308
313
  # When using a very large number of Consul Endpoints
@@ -329,7 +334,7 @@ Signal.trap('USR1', 'IGNORE') unless Gem.win_platform?
329
334
  Signal.trap(sig) do |signo|
330
335
  # handle nicely forks
331
336
  m_p = my_pid == Process.pid
332
- STDERR.puts "[KILL] received #{Signal.signame(signo)}, stopping myself" if m_p
337
+ warn "[KILL] received #{Signal.signame(signo)}, stopping myself" if m_p
333
338
  template_manager.terminate
334
339
  kill_program if m_p
335
340
  end
@@ -338,7 +343,7 @@ end
338
343
  begin
339
344
  consul_engine.run(template_manager)
340
345
  rescue Interrupt => e
341
- STDERR.puts "[WARN] consul-templaterb has been interrupted #{e}"
346
+ warn "[WARN] consul-templaterb has been interrupted #{e}"
342
347
  end
343
348
 
344
349
  exit consul_engine.result
@@ -1,6 +1,4 @@
1
- # coding: utf-8
2
-
3
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
4
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
3
  require 'consul/async/version'
6
4
 
@@ -16,8 +14,8 @@ Gem::Specification.new do |spec|
16
14
  spec.description = 'A ruby implementation of Consul Template with support of erb templating ' \
17
15
  'with hi-performance on large clusters and advanced process management features.'
18
16
  spec.metadata = { 'bug_tracker_uri' => 'https://github.com/criteo/consul-templaterb/issues',
19
- 'changelog_uri' => 'https://github.com/criteo/consul-templaterb/blob/master/CHANGELOG.md',
20
- 'homepage_uri' => 'https://github.com/criteo/consul-templaterb',
17
+ 'changelog_uri' => 'https://github.com/criteo/consul-templaterb/blob/master/CHANGELOG.md',
18
+ 'homepage_uri' => 'https://github.com/criteo/consul-templaterb',
21
19
  'source_code_uri' => 'https://github.com/criteo/consul-templaterb' }
22
20
  spec.license = 'Apache v2'
23
21
 
@@ -29,7 +27,6 @@ Gem::Specification.new do |spec|
29
27
  spec.extra_rdoc_files = ['README.md', 'CHANGELOG.md', 'TemplateAPI.md']
30
28
  spec.require_paths = ['lib']
31
29
 
32
- spec.require_paths = ['lib']
33
30
  spec.add_runtime_dependency 'em-http-request', '>= 1.1.5'
34
31
  spec.add_runtime_dependency 'eventmachine', '>= 1.2.7'
35
32
  spec.add_runtime_dependency 'parallel', '>= 1.2.7'
@@ -38,7 +35,7 @@ Gem::Specification.new do |spec|
38
35
  spec.add_development_dependency 'rake', '~> 10.0'
39
36
  spec.add_development_dependency 'rspec', '~> 3.0'
40
37
  spec.add_development_dependency 'rspec_junit_formatter'
41
- spec.add_development_dependency 'rubocop', '0.49.0'
38
+ spec.add_development_dependency 'rubocop', '0.80.0'
42
39
  spec.add_development_dependency 'rubocop-junit-formatter'
43
40
  spec.add_development_dependency 'webmock'
44
41
  end
@@ -1,10 +1,11 @@
1
1
  require 'consul/async/utilities'
2
2
  require 'consul/async/stats'
3
3
  require 'em-http'
4
- require 'thread'
5
4
  require 'json'
6
5
  module Consul
7
6
  module Async
7
+ # The class configuring Consul endpoints
8
+ # It allows to translate configuration options per specific endpoint/path
8
9
  class ConsulConfiguration
9
10
  attr_reader :base_url, :token, :retry_duration, :min_duration, :wait_duration, :max_retry_duration, :retry_on_non_diff,
10
11
  :missing_index_retry_time_on_diff, :missing_index_retry_time_on_unchanged, :debug, :enable_gzip_compression,
@@ -51,6 +52,7 @@ module Consul
51
52
 
52
53
  def create(path)
53
54
  return self unless @paths[path.to_sym]
55
+
54
56
  ConsulConfiguration.new(base_url: ch(path, :base_url),
55
57
  debug: ch(path, :debug),
56
58
  token: ch(path, :token),
@@ -67,6 +69,9 @@ module Consul
67
69
  fail_fast_errors: @fail_fast_errors)
68
70
  end
69
71
  end
72
+
73
+ # This keep track of answer from Consul
74
+ # It also keep statistics about result (x_consul_index, stats...)
70
75
  class ConsulResult
71
76
  attr_reader :data, :http, :x_consul_index, :last_update, :stats, :retry_in
72
77
  def initialize(data, modified, http, x_consul_index, stats, retry_in, fake: false)
@@ -102,6 +107,8 @@ module Consul
102
107
  next_retry + last_update
103
108
  end
104
109
  end
110
+ # Basic Encapsulation of HTTP response from Consul
111
+ # It supports empty responses to handle first call is an easy way
105
112
  class HttpResponse
106
113
  attr_reader :response_header, :response, :error
107
114
  def initialize(http, override_nil_response = nil)
@@ -116,6 +123,9 @@ module Consul
116
123
  end
117
124
  end
118
125
  end
126
+ # This class represents a specific path in Consul HTTP API
127
+ # It also stores x_consul_index and keep track on updates of API
128
+ # So, it basically performs all the optimizations to keep updated with Consul internal state.
119
129
  class ConsulEndpoint
120
130
  attr_reader :conf, :path, :x_consul_index, :queue, :stats, :last_result, :enforce_json_200, :start_time, :default_value, :query_params
121
131
  def initialize(conf, path, enforce_json_200 = true, query_params = {}, default_value = '[]')
@@ -144,12 +154,12 @@ module Consul
144
154
  on_response do |result|
145
155
  state = result.x_consul_index.to_i < 1 ? '[WARN]' : '[ OK ]'
146
156
  stats = result.stats
147
- STDERR.puts "[DBUG]#{state}#{result.modified? ? '[MODFIED]' : '[NO DIFF]'}" \
157
+ warn "[DBUG]#{state}#{result.modified? ? '[MODFIED]' : '[NO DIFF]'}" \
148
158
  "[s:#{stats.successes},err:#{stats.errors}]" \
149
159
  "[#{stats.body_bytes_human.ljust(8)}][#{stats.bytes_per_sec_human.ljust(9)}]"\
150
160
  " #{path.ljust(48)} idx:#{result.x_consul_index}, next in #{result.retry_in} s"
151
161
  end
152
- on_error { |http| STDERR.puts "[ERROR]: #{path}: #{http.error.inspect}" }
162
+ on_error { |http| warn "[ERROR]: #{path}: #{http.error.inspect}" }
153
163
  end
154
164
 
155
165
  def on_response(&block)
@@ -173,7 +183,7 @@ module Consul
173
183
  def build_request(consul_index)
174
184
  res = {
175
185
  head: {
176
- 'Accept' => 'application/json',
186
+ 'Accept' => 'application/json',
177
187
  'X-Consul-Index' => consul_index,
178
188
  'X-Consul-Token' => conf.token
179
189
  },
@@ -216,7 +226,7 @@ module Consul
216
226
  def fetch
217
227
  options = {
218
228
  connect_timeout: 5, # default connection setup timeout
219
- inactivity_timeout: conf.wait_duration + 1 + (conf.wait_duration / 16), # default connection inactivity (post-setup) timeout
229
+ inactivity_timeout: conf.wait_duration + 1 + (conf.wait_duration / 16) # default connection inactivity (post-setup) timeout
220
230
  }
221
231
  connection = {
222
232
  conn: EventMachine::HttpRequest.new(conf.base_url, options)
@@ -228,7 +238,7 @@ module Consul
228
238
  is_kv_empty = path.start_with?('/v1/kv') && http.response_header.status == 404
229
239
  if !is_kv_empty && enforce_json_200 && http.response_header.status != 200 && http.response_header['Content-Type'] != 'application/json'
230
240
  _handle_error(http, consul_index) do
231
- STDERR.puts "[RETRY][#{path}] (#{@consecutive_errors} errors)" if (@consecutive_errors % 10) == 1
241
+ warn "[RETRY][#{path}] (#{@consecutive_errors} errors)" if (@consecutive_errors % 10) == 1
232
242
  end
233
243
  else
234
244
  n_consul_index = find_x_consul_index(http)
@@ -1,12 +1,13 @@
1
1
  require 'consul/async/utilities'
2
2
  require 'em-http'
3
- require 'thread'
4
3
  require 'forwardable'
5
4
  require 'erb'
6
5
  require 'digest'
7
6
 
8
7
  module Consul
9
8
  module Async
9
+ # Exception thrown when a template is invalid and cause a Runtime Exception during
10
+ # its rendering
10
11
  class InvalidTemplateException < StandardError
11
12
  attr_reader :cause
12
13
  def initialize(cause)
@@ -14,6 +15,7 @@ module Consul
14
15
  end
15
16
  end
16
17
 
18
+ # Exception thrown when the template is Invalid due to a Syntax Error
17
19
  class SyntaxErrorInTemplate < InvalidTemplateException
18
20
  attr_reader :cause
19
21
  def initialize(cause)
@@ -21,6 +23,7 @@ module Consul
21
23
  end
22
24
  end
23
25
 
26
+ # Class to handle the retrival of a Remote resource (such a JSON API)
24
27
  class RemoteResource
25
28
  def initialize(endpoints_manager)
26
29
  @endp_manager = endpoints_manager
@@ -78,6 +81,10 @@ module Consul
78
81
  end
79
82
  end
80
83
 
84
+ # This class keep references over all endpoints (aka datasources) registered for all templates.
85
+ # This allows reusing those endpoints as well as performing listing and garbage collecting.
86
+ # This is also the main object visible from ERB files which contains all methods available
87
+ # to template writters.
81
88
  class EndPointsManager
82
89
  attr_reader :consul_conf, :vault_conf, :running, :net_info, :start_time, :coordinate, :remote_resource, :templates
83
90
  def initialize(consul_configuration, vault_configuration, templates, trim_mode = nil)
@@ -100,9 +107,9 @@ module Consul
100
107
  @context = {
101
108
  current_erb_path: nil,
102
109
  template_info: {
103
- 'source_root' => nil,
104
- 'source' => nil,
105
- 'destination' => nil,
110
+ 'source_root' => nil,
111
+ 'source' => nil,
112
+ 'destination' => nil,
106
113
  'was_rendered_once' => false
107
114
  },
108
115
  params: {}
@@ -119,6 +126,7 @@ module Consul
119
126
  # https://www.consul.io/api/health.html#list-nodes-for-service
120
127
  def service(name, dc: nil, passing: false, tag: nil)
121
128
  raise 'You must specify a name for a service' if name.nil?
129
+
122
130
  path = "/v1/health/service/#{name}"
123
131
  query_params = {}
124
132
  query_params[:dc] = dc if dc
@@ -130,6 +138,7 @@ module Consul
130
138
  # https://www.consul.io/api/health.html#list-checks-for-service
131
139
  def checks_for_service(name, dc: nil, passing: false)
132
140
  raise 'You must specify a name for a service' if name.nil?
141
+
133
142
  path = "/v1/health/checks/#{name}"
134
143
  query_params = {}
135
144
  query_params[:dc] = dc if dc
@@ -140,6 +149,7 @@ module Consul
140
149
  # https://www.consul.io/api/health.html#list-checks-for-node
141
150
  def checks_for_node(name, dc: nil, passing: false)
142
151
  raise 'You must specify a name for a service' if name.nil?
152
+
143
153
  path = "/v1/health/node/#{name}"
144
154
  query_params = {}
145
155
  query_params[:dc] = dc if dc
@@ -182,8 +192,8 @@ module Consul
182
192
  # Return a param of template
183
193
  def param(key, default_value = nil)
184
194
  v = @context[:params][key]
185
- v = @context[:params][key.to_sym] unless v
186
- v = default_value unless v
195
+ v ||= @context[:params][key.to_sym]
196
+ v ||= default_value
187
197
  v
188
198
  end
189
199
 
@@ -222,6 +232,7 @@ module Consul
222
232
 
223
233
  def secrets(path = '')
224
234
  raise "You need to provide a vault token to use 'secret' keyword" if vault_conf.token.nil?
235
+
225
236
  path = "/v1/#{path}".gsub(%r{/{2,}}, '/')
226
237
  query_params = { list: 'true' }
227
238
  create_if_missing(path, query_params, vault_conf.fail_fast_errors, vault_conf.max_consecutive_errors_on_endpoint) do
@@ -231,6 +242,7 @@ module Consul
231
242
 
232
243
  def secret(path = '', post_data = nil)
233
244
  raise "You need to provide a vault token to use 'secret' keyword" if vault_conf.token.nil?
245
+
234
246
  path = "/v1/#{path}".gsub(%r{/{2,}}, '/')
235
247
  query_params = {}
236
248
  method = post_data ? 'POST' : 'GET'
@@ -243,12 +255,14 @@ module Consul
243
255
  def render_file(path, params = {})
244
256
  new_path = File.expand_path(path, File.dirname(@context[:current_erb_path]))
245
257
  raise "render_file ERROR: #{path} is resolved as #{new_path}, but the file does not exists" unless File.exist? new_path
258
+
246
259
  render(File.read(new_path), new_path, params, current_template_info: template_info)
247
260
  end
248
261
 
249
262
  # render a sub template from a string template
250
263
  def render_from_string(template_content, params = {})
251
264
  return unless template_content
265
+
252
266
  sha1res = Digest::SHA1.hexdigest(template_content)
253
267
  new_path = File.expand_path(":memory:sha1:#{sha1res}", File.dirname(@context[:current_erb_path]))
254
268
  render(template_content, new_path, params, current_template_info: template_info)
@@ -256,6 +270,7 @@ module Consul
256
270
 
257
271
  def find_line(e)
258
272
  return e.message.dup[5..-1] if e.message.start_with? '(erb):'
273
+
259
274
  e.backtrace.each do |line|
260
275
  return line[5..-1] if line.start_with? '(erb):'
261
276
  end
@@ -273,6 +288,7 @@ module Consul
273
288
  }
274
289
  result = ERB.new(tpl, nil, @trim_mode).result(binding)
275
290
  raise "Result is not a string :='#{result}' for #{tpl_file_path}" unless result.is_a?(String)
291
+
276
292
  @context = old_value
277
293
  result
278
294
  rescue StandardError => e
@@ -380,6 +396,7 @@ module Consul
380
396
  end
381
397
  end
382
398
 
399
+ # Abstract class that stores information about a result
383
400
  class ConsulTemplateAbstract
384
401
  attr_reader :result, :endpoint, :seen_at
385
402
  def initialize(consul_endpoint)
@@ -421,12 +438,14 @@ module Consul
421
438
  end
422
439
  end
423
440
 
441
+ # Concrete class of a result when the result is a JSON Object
424
442
  class ConsulTemplateAbstractMap < ConsulTemplateAbstract
425
443
  def initialize(consul_endpoint)
426
444
  super(consul_endpoint)
427
445
  end
428
446
  end
429
447
 
448
+ # Concrete class of a result when the result is a JSON Array
430
449
  class ConsulTemplateAbstractArray < ConsulTemplateAbstract
431
450
  def initialize(consul_endpoint)
432
451
  super(consul_endpoint)
@@ -436,8 +455,11 @@ module Consul
436
455
  # technically this class could be also an array, a simple string or any simple json object other than a hash.
437
456
  class ConsulTemplateAbstractJSONObject < ConsulTemplateAbstractMap; end
438
457
 
458
+ # Just another name
439
459
  class ConsulTemplateAbstractJSONArray < ConsulTemplateAbstractArray; end
440
460
 
461
+ # The ServiceInstance has shortcuts (such as service_address method), but is
462
+ # basically a Hash.
441
463
  class ServiceInstance < Hash
442
464
  def initialize(obj)
443
465
  merge!(obj)
@@ -471,6 +493,7 @@ module Consul
471
493
  ret = 'passing'
472
494
  checks = self['Checks']
473
495
  return ret unless checks
496
+
474
497
  checks.each do |chk|
475
498
  st = chk['Status']
476
499
  if st == 'critical'
@@ -494,6 +517,7 @@ module Consul
494
517
  end
495
518
  end
496
519
 
520
+ # Representation as a Map of a Service (includes Service, Node, Checks)
497
521
  class ConsulTemplateService < ConsulTemplateAbstractMap
498
522
  def initialize(consul_endpoint)
499
523
  super(consul_endpoint)
@@ -503,6 +527,7 @@ module Consul
503
527
 
504
528
  def result_delegate
505
529
  return @cached_result if @cached_json == result.json
530
+
506
531
  new_res = []
507
532
  result.json.each do |v|
508
533
  new_res << ServiceInstance.new(v)
@@ -513,12 +538,14 @@ module Consul
513
538
  end
514
539
  end
515
540
 
541
+ # Object returned by datacenters(), basically a JSON Array
516
542
  class ConsulTemplateDatacenters < ConsulTemplateAbstractArray
517
543
  def initialize(consul_endpoint)
518
544
  super(consul_endpoint)
519
545
  end
520
546
  end
521
547
 
548
+ # Object returned by services() an abstract map of service_name, tags
522
549
  class ConsulTemplateServices < ConsulTemplateAbstractMap
523
550
  def initialize(consul_endpoint)
524
551
  super(consul_endpoint)
@@ -526,6 +553,7 @@ module Consul
526
553
 
527
554
  def parse_result(res)
528
555
  return res unless res.data == '{}' || endpoint.query_params[:tag]
556
+
529
557
  res_json = JSON.parse(res.data)
530
558
  result = {}
531
559
  res_json.each do |name, tags|
@@ -536,33 +564,43 @@ module Consul
536
564
  end
537
565
  end
538
566
 
567
+ # Another name to handle backwards compatibility
539
568
  class ConsulTemplateJSONObject < ConsulTemplateAbstractJSONObject; end
569
+
570
+ # Another name to handle backwards compatibility
540
571
  class ConsulTemplateJSONArray < ConsulTemplateAbstractJSONArray; end
541
572
 
573
+ # Object returned by /v1/agent/self, a JSON Map
542
574
  class ConsulAgentSelf < ConsulTemplateAbstractMap
543
575
  def initialize(consul_endpoint)
544
576
  super(consul_endpoint)
545
577
  end
546
578
  end
547
579
 
580
+ # Object returning metrics from Consul agent, a JSON Map
548
581
  class ConsulAgentMetrics < ConsulTemplateAbstractMap
549
582
  def initialize(consul_endpoint)
550
583
  super(consul_endpoint)
551
584
  end
552
585
  end
553
586
 
587
+ # List of checks for agent
554
588
  class ConsulTemplateChecks < ConsulTemplateAbstractArray
555
589
  def initialize(consul_endpoint)
556
590
  super(consul_endpoint)
557
591
  end
558
592
  end
559
593
 
594
+ # List of nodes of the whole cluster
560
595
  class ConsulTemplateNodes < ConsulTemplateAbstractArray
561
596
  def initialize(consul_endpoint)
562
597
  super(consul_endpoint)
563
598
  end
564
599
  end
565
600
 
601
+ # Key/Values representations
602
+ # This is an array as it might contain several values
603
+ # Several helpers exist to handle nicely transformations
566
604
  class ConsulTemplateKV < ConsulTemplateAbstractArray
567
605
  attr_reader :root
568
606
  def initialize(consul_endpoint, root)
@@ -584,6 +622,7 @@ module Consul
584
622
  def get_value_decoded(name = root)
585
623
  val = get_value(name)
586
624
  return nil unless val
625
+
587
626
  Base64.decode64(val)
588
627
  end
589
628
 
@@ -591,10 +630,12 @@ module Consul
591
630
  def get_value_json(name = root, catch_errors: true)
592
631
  x = get_value_decoded(name)
593
632
  return nil unless x
633
+
594
634
  begin
595
635
  JSON.parse(x)
596
636
  rescue JSON::ParserError => e
597
637
  return nil if catch_errors
638
+
598
639
  raise StandardError.new(e), "get_value_json() cannot deserialize kv(#{name}) as JSON: #{e.message}", e.backtrace
599
640
  end
600
641
  end
@@ -603,23 +644,29 @@ module Consul
603
644
  def get_value_yaml(name = root, catch_errors: true)
604
645
  x = get_value_decoded(name)
605
646
  return nil unless x
647
+
606
648
  begin
607
649
  YAML.safe_load(x)
608
650
  rescue YAML::ParserError => e
609
651
  return nil if catch_errors
652
+
610
653
  raise StandardError.new(e), "get_value_yaml() cannot deserialize kv(#{name}) as YAML: #{e.message}", e.backtrace
611
654
  end
612
655
  end
613
656
  end
614
657
 
658
+ # Vault Secrets is a Map of secrets properly decoded
615
659
  class ConsulTemplateVaultSecret < ConsulTemplateAbstractMap
616
660
  def initialize(vault_endpoint)
617
661
  super(vault_endpoint)
618
662
  end
619
663
  end
664
+
665
+ # Array of available secrets
620
666
  class ConsulTemplateVaultSecretList < ConsulTemplateAbstractArray
621
667
  def parse_result(res)
622
668
  return res if res.data.nil?
669
+
623
670
  res.mutate(JSON.generate(res.json['data']['keys']))
624
671
  res
625
672
  end
@@ -5,10 +5,11 @@ require 'consul/async/vault_endpoint'
5
5
  require 'consul/async/consul_template'
6
6
  require 'consul/async/consul_template_render'
7
7
  require 'em-http'
8
- require 'thread'
9
8
  require 'erb'
10
9
  module Consul
11
10
  module Async
11
+ # The Engine keeps tracks of all templates, handle hot-reload of files if needed
12
+ # as well as ticking the clock to compute state of templates periodically.
12
13
  class ConsulTemplateEngine
13
14
  attr_reader :template_manager, :hot_reload_failure, :template_frequency, :debug_memory, :result, :templates
14
15
  attr_writer :hot_reload_failure, :template_frequency, :debug_memory
@@ -61,17 +62,17 @@ module Consul
61
62
  @template_callbacks.each do |c|
62
63
  c.call([all_ready, template_manager, results])
63
64
  end
64
- rescue StandardError => cbk_error
65
- ::Consul::Async::Debug.puts_error "callback error: #{cbk_error.inspect}"
66
- raise cbk_error
65
+ rescue StandardError => e
66
+ ::Consul::Async::Debug.puts_error "callback error: #{e.inspect}"
67
+ raise e
67
68
  end
68
69
  rescue Consul::Async::InvalidTemplateException => e
69
- STDERR.puts "[FATAL]#{e}"
70
+ warn "[FATAL]#{e}"
70
71
  template_manager.terminate
71
72
  @result = 1
72
73
  EventMachine.stop
73
74
  rescue StandardError => e
74
- STDERR.puts "[FATAL] Error occured: #{e.inspect} - #{e.backtrace.join("\n\t")}"
75
+ warn "[FATAL] Error occured: #{e.inspect} - #{e.backtrace.join("\n\t")}"
75
76
  template_manager.terminate
76
77
  @result = 2
77
78
  EventMachine.stop
@@ -82,6 +83,7 @@ module Consul
82
83
  do_run(template_manager, template_renders)
83
84
 
84
85
  return if @all_templates_rendered || @periodic_started
86
+
85
87
  # We continue if rendering not done and periodic not started
86
88
  EventMachine.next_tick do
87
89
  do_run_fast(template_manager, template_renders)
@@ -111,7 +113,7 @@ module Consul
111
113
  diff_num_objects = new_memory_state[:objects] - @last_memory_state[:objects]
112
114
  if diff_allocated != 0 || diff_num_objects.abs > (@last_memory_state[:pages] / 3)
113
115
  timediff = new_memory_state[:time] - @last_memory_state[:time]
114
- STDERR.puts "[MEMORY] #{new_memory_state[:time]} significant RAM Usage detected\n" \
116
+ warn "[MEMORY] #{new_memory_state[:time]} significant RAM Usage detected\n" \
115
117
  "[MEMORY] #{new_memory_state[:time]} Pages : #{new_memory_state[:pages]}" \
116
118
  " (diff #{diff_allocated} aka #{(diff_allocated / timediff).round(0)}/s) \n" \
117
119
  "[MEMORY] #{new_memory_state[:time]} Objects: #{new_memory_state[:objects]}"\
@@ -1,9 +1,10 @@
1
1
  require 'consul/async/utilities'
2
2
  require 'em-http'
3
- require 'thread'
4
3
  require 'erb'
5
4
  module Consul
6
5
  module Async
6
+ # Result of the rendering of a template
7
+ # .ready? will tell if rendering is full or partial
7
8
  class ConsulTemplateRenderedResult
8
9
  attr_reader :template_file, :output_file, :hot_reloaded, :ready, :modified, :last_result
9
10
  def initialize(template_file, output_file, hot_reloaded, was_success, modified, last_result)
@@ -19,6 +20,9 @@ module Consul
19
20
  @ready
20
21
  end
21
22
  end
23
+ # Object handling the whole rendering of a template
24
+ # It stores the input, the output and flags about last result being modified or simply readiness
25
+ # information about whether the template did receive all data to be fully rendered
22
26
  class ConsulTemplateRender
23
27
  attr_reader :template_file, :output_file, :template_file_ctime, :hot_reload_failure, :params
24
28
  def initialize(template_manager, template_file, output_file, hot_reload_failure: 'die', params: {})
@@ -53,6 +57,7 @@ module Consul
53
57
  # Will throw Consul::Async::InvalidTemplateException if template invalid
54
58
  def update_template(new_template, current_template_info)
55
59
  return false unless new_template != @template
60
+
56
61
  # We render to ensure the template is valid
57
62
  render(new_template, current_template_info)
58
63
  @template = new_template.freeze
@@ -61,9 +66,9 @@ module Consul
61
66
 
62
67
  def _build_default_template_info
63
68
  {
64
- 'destination' => @output_file,
65
- 'source_root' => template_file.freeze,
66
- 'source' => template_file.freeze,
69
+ 'destination' => @output_file,
70
+ 'source_root' => template_file.freeze,
71
+ 'source' => template_file.freeze,
67
72
  'was_rendered_once' => @was_rendered_once
68
73
  }
69
74
  end
@@ -72,7 +77,7 @@ module Consul
72
77
  current_template_info = _build_default_template_info
73
78
  success, modified, last_res = @template_manager.write(@output_file, @template, @last_result, template_file, params, current_template_info: current_template_info)
74
79
  @last_result = last_res if last_res
75
- @was_rendered_once = success unless @was_rendered_once
80
+ @was_rendered_once ||= success
76
81
  [success, modified, @last_result]
77
82
  end
78
83
 
@@ -84,9 +89,10 @@ module Consul
84
89
  @template_file_ctime = new_time
85
90
  return update_template(load_template, current_template_info: current_template_info)
86
91
  rescue Consul::Async::InvalidTemplateException => e
87
- STDERR.puts "****\n[ERROR] HOT Reload of template #{template_file} did fail due to:\n #{e}\n****\n template_info: #{current_template_info}\n****"
92
+ warn "****\n[ERROR] HOT Reload of template #{template_file} did fail due to:\n #{e}\n****\n template_info: #{current_template_info}\n****"
88
93
  raise e unless hot_reload_failure == 'keep'
89
- STDERR.puts "[WARN] Hot reload of #{template_file} was not taken into account, keep running with previous version"
94
+
95
+ warn "[WARN] Hot reload of #{template_file} was not taken into account, keep running with previous version"
90
96
  end
91
97
  end
92
98
  false
@@ -1,5 +1,6 @@
1
1
  module Consul
2
2
  module Async
3
+ # Toolbox for logs
3
4
  class Debug
4
5
  def self.level
5
6
  @level || 2
@@ -12,15 +13,16 @@ module Consul
12
13
  def self.level=(log_level)
13
14
  lvl = levels.index(log_level)
14
15
  raise "Log level #{log_level} unsupported, must be one of #{levels.inspect}" if lvl.nil?
16
+
15
17
  @level = lvl
16
18
  end
17
19
 
18
20
  def self.puts_error(msg)
19
- STDERR.puts "[ERROR] #{msg}" if level.positive?
21
+ warn "[ERROR] #{msg}" if level.positive?
20
22
  end
21
23
 
22
24
  def self.puts_info(msg)
23
- STDERR.puts "[INFO] #{msg}" if level > 1
25
+ warn "[INFO] #{msg}" if level > 1
24
26
  end
25
27
 
26
28
  def self.print_info(msg)
@@ -28,7 +30,7 @@ module Consul
28
30
  end
29
31
 
30
32
  def self.puts_debug(msg)
31
- STDERR.puts "[DEBG] #{msg}" if level > 2
33
+ warn "[DEBG] #{msg}" if level > 2
32
34
  end
33
35
 
34
36
  def self.print_debug(msg)
@@ -5,6 +5,7 @@ require 'json'
5
5
 
6
6
  module Consul
7
7
  module Async
8
+ # Configuration to apply to JSONEndpoints
8
9
  class JSONConfiguration
9
10
  attr_reader :url, :retry_duration, :min_duration, :retry_on_non_diff,
10
11
  :debug, :enable_gzip_compression, :request_method, :json_body,
@@ -34,6 +35,7 @@ module Consul
34
35
  self
35
36
  end
36
37
  end
38
+ # Result from call to a Remote JSON endpoint
37
39
  class JSONResult
38
40
  attr_reader :data, :http, :last_update, :stats, :retry_in
39
41
  def initialize(data, modified, http, stats, retry_in, fake: false)
@@ -67,6 +69,7 @@ module Consul
67
69
  next_retry + last_update
68
70
  end
69
71
  end
72
+ # Encapsulation of HTTP Response
70
73
  class HttpResponse
71
74
  attr_reader :response_header, :response, :error
72
75
  def initialize(http, override_nil_response = nil)
@@ -81,6 +84,7 @@ module Consul
81
84
  end
82
85
  end
83
86
  end
87
+ # Endpoint (aka URL) of a remote API that might be called
84
88
  class JSONEndpoint
85
89
  attr_reader :conf, :url, :queue, :stats, :last_result, :enforce_json_200, :start_time, :default_value, :query_params
86
90
  def initialize(conf, url, default_value, enforce_json_200 = true, query_params = {})
@@ -1,11 +1,16 @@
1
1
  module Consul
2
2
  module Async
3
+ # Exception thrown when process does not exists
3
4
  class ProcessDoesNotExist < StandardError
4
5
  end
6
+
7
+ # Handle the full lifecycle of a process and allows to forward
8
+ # Posix signals to child process when needed.
5
9
  class ProcessHandler
6
10
  attr_reader :command, :sig_reload, :sig_term, :pid, :exit_status
7
11
  def initialize(command, sig_reload: 'HUP', sig_term: 'TERM')
8
12
  raise 'empty sig_term is not supported' unless sig_term
13
+
9
14
  @command = command
10
15
  @sig_reload = sig_reload
11
16
  @sig_term = sig_term
@@ -15,44 +20,49 @@ module Consul
15
20
 
16
21
  def start
17
22
  return pid unless pid.nil?
23
+
18
24
  @pid = Process.spawn(command)
19
25
  end
20
26
 
21
27
  def reload
22
28
  return if sig_reload.nil?
23
- STDERR.puts "Sending SIG #{sig_reload} to #{pid}..."
29
+
30
+ warn "Sending SIG #{sig_reload} to #{pid}..."
24
31
  begin
25
32
  Process.kill(sig_reload, pid)
26
33
  rescue Errno::ESRCH => e
27
- STDERR.puts "*** Process #{pid} has already been killed: #{e.inspect}"
34
+ warn "*** Process #{pid} has already been killed: #{e.inspect}"
28
35
  raise e
29
36
  end
30
37
  end
31
38
 
32
39
  def kill
33
40
  return exit_status if pid.nil?
41
+
34
42
  the_pid = pid
35
43
  @pid = nil
36
- STDERR.puts "[KILL] Sending SIG #{sig_term} to #{the_pid}..."
44
+ warn "[KILL] Sending SIG #{sig_term} to #{the_pid}..."
37
45
  begin
38
- STDERR.puts "[KILL] waiting for #{the_pid}..."
46
+ warn "[KILL] waiting for #{the_pid}..."
39
47
  Process.kill(sig_term, the_pid)
40
48
  rescue Errno::ESRCH
41
- STDERR.puts "[KILL] *** Process #{the_pid} has already been killed"
49
+ warn "[KILL] *** Process #{the_pid} has already been killed"
42
50
  end
43
51
  begin
44
52
  _pid, @exit_status = Process.waitpid2 the_pid
45
53
  rescue SystemCallError
46
- STDERR.puts "[KILL] *** UNEXPECTED ERROR *** Failed to get return code for #{the_pid}"
54
+ warn "[KILL] *** UNEXPECTED ERROR *** Failed to get return code for #{the_pid}"
47
55
  end
48
56
  exit_status
49
57
  end
50
58
 
51
59
  def process_status
52
60
  raise ProcessDoesNotExist, 'No child process' if pid.nil?
61
+
53
62
  begin
54
63
  cpid, result = Process.waitpid2(pid, Process::WNOHANG)
55
64
  raise ProcessDoesNotExist, "Unexpected PID: #{cpid}, was expecting #{pid}" unless cpid.nil? || cpid == pid
65
+
56
66
  result
57
67
  rescue Errno::ECHILD => e
58
68
  e2 = ProcessDoesNotExist.new e
@@ -2,6 +2,8 @@ require 'consul/async/utilities'
2
2
 
3
3
  module Consul
4
4
  module Async
5
+ # Object keeping stats about a single Endpoint
6
+ # Accessible within the .stats of a EndPoint
5
7
  class EndPointStats
6
8
  attr_reader :successes, :errors, :consecutive_errors, :start, :body_bytes, :last_error, :last_success, :last_modified, :changes, :network_bytes
7
9
 
@@ -3,6 +3,7 @@ require 'yaml'
3
3
  require 'json'
4
4
  module Consul
5
5
  module Async
6
+ # Various utilities
6
7
  class Utilities
7
8
  def self.bytes_to_h(bytes)
8
9
  if bytes < 1024
@@ -25,6 +26,7 @@ module Consul
25
26
  # Loads parameters from a file, supports JSON and YAML
26
27
  def self.load_parameters_from_file(parameters_file)
27
28
  raise "Parameters file #{parameters_file} does not exists" unless File.exist? parameters_file
29
+
28
30
  if parameters_file.downcase.end_with?('.yaml', '.yml')
29
31
  YAML.load_file(parameters_file)
30
32
  elsif parameters_file.downcase.end_with?('.json')
@@ -35,7 +37,7 @@ module Consul
35
37
  end
36
38
 
37
39
  def self.random
38
- @random = Random.new unless @random
40
+ @random ||= Random.new
39
41
  @random
40
42
  end
41
43
  end
@@ -3,11 +3,11 @@ require 'consul/async/stats'
3
3
  require 'consul/async/debug'
4
4
  require 'em-http'
5
5
  require 'net/http'
6
- require 'thread'
7
6
  require 'json'
8
7
 
9
8
  module Consul
10
9
  module Async
10
+ # Configuration for Vault Endpoints
11
11
  class VaultConfiguration
12
12
  attr_reader :base_url, :token, :token_renew, :retry_duration, :min_duration, :wait_duration, :max_retry_duration, :retry_on_non_diff,
13
13
  :lease_duration_factor, :debug, :max_consecutive_errors_on_endpoint, :fail_fast_errors
@@ -48,6 +48,7 @@ module Consul
48
48
 
49
49
  def create(path)
50
50
  return self unless @paths[path.to_sym]
51
+
51
52
  VaultConfiguration.new(base_url: ch(path, :base_url),
52
53
  debug: ch(path, :debug),
53
54
  token: ch(path, :token),
@@ -60,6 +61,7 @@ module Consul
60
61
  fail_fast_errors: @fail_fast_errors)
61
62
  end
62
63
  end
64
+ # Keep information about Vault result of a query
63
65
  class VaultResult
64
66
  attr_reader :data, :http, :stats, :retry_in
65
67
 
@@ -92,6 +94,8 @@ module Consul
92
94
  @data_json
93
95
  end
94
96
  end
97
+
98
+ # VaultHttpResponse supports empty results (when no data has been received yet)
95
99
  class VaultHttpResponse
96
100
  attr_reader :response_header, :response, :error, :json
97
101
 
@@ -109,6 +113,7 @@ module Consul
109
113
  end
110
114
  end
111
115
 
116
+ # Endpoint in vault (a path in Vault)
112
117
  class VaultEndpoint
113
118
  attr_reader :conf, :path, :http_method, :queue, :stats, :last_result, :enforce_json_200, :start_time, :default_value, :query_params
114
119
 
@@ -140,7 +145,7 @@ module Consul
140
145
  on_response do |result|
141
146
  state = result.x_consul_index.to_i < 1 ? '[WARN]' : '[ OK ]'
142
147
  stats = result.stats
143
- STDERR.puts "[DBUG]#{state}#{result.modified? ? '[MODFIED]' : '[NO DIFF]'}" \
148
+ warn "[DBUG]#{state}#{result.modified? ? '[MODFIED]' : '[NO DIFF]'}" \
144
149
  "[s:#{stats.successes},err:#{stats.errors}]" \
145
150
  "[#{stats.body_bytes_human.ljust(8)}][#{stats.bytes_per_sec_human.ljust(9)}]"\
146
151
  " #{path.ljust(48)} idx:#{result.x_consul_index}, next in #{result.retry_in} s"
@@ -192,6 +197,7 @@ module Consul
192
197
 
193
198
  def _get_errors(http)
194
199
  return [http.error] if http.error
200
+
195
201
  unless http.json.nil?
196
202
  return http.json['errors'] if http.json.key?('errors')
197
203
  end
@@ -213,7 +219,7 @@ module Consul
213
219
  def fetch
214
220
  options = {
215
221
  connect_timeout: 5, # default connection setup timeout
216
- inactivity_timeout: 1, # default connection inactivity (post-setup) timeout
222
+ inactivity_timeout: 1 # default connection inactivity (post-setup) timeout
217
223
  }
218
224
  connection = EventMachine::HttpRequest.new(conf.base_url, options)
219
225
  cb = proc do |_|
@@ -241,9 +247,7 @@ module Consul
241
247
  end
242
248
 
243
249
  http.errback do
244
- unless @stopping
245
- _handle_error(http) { connection = EventMachine::HttpRequest.new(conf.base_url, options) }
246
- end
250
+ _handle_error(http) { connection = EventMachine::HttpRequest.new(conf.base_url, options) } unless @stopping
247
251
  end
248
252
  queue.pop(&cb)
249
253
  end
@@ -1,5 +1,5 @@
1
1
  module Consul
2
2
  module Async
3
- VERSION = '1.23.0'.freeze
3
+ VERSION = '1.24.0'.freeze
4
4
  end
5
5
  end
@@ -250,3 +250,7 @@ pre, code {
250
250
  .connect-disabled {
251
251
  display: none;
252
252
  }
253
+
254
+ .hidden {
255
+ display: none;
256
+ }
@@ -153,7 +153,6 @@ function decorateIsoDate(value, formated) {
153
153
  return document.createTextNode(value);
154
154
  }
155
155
  const d = new Date(parsed);
156
- console.log(d);
157
156
  const e = document.createElement('time');
158
157
  e.setAttribute('datetime', formated);
159
158
  e.setAttribute('title', value);
@@ -223,6 +222,64 @@ function service_meta_semantics_decorator(instance, key, value, serviceName) {
223
222
  return fun(instance, key, value);
224
223
  }
225
224
 
225
+ const node_meta_decorators = {
226
+ 'os': function(k, v, nodeMetaTags, serviceMetaIfAny) {
227
+ var e = document.createElement('span');
228
+ e.setAttribute('class', 'badge badge-light mx-1 lookup');
229
+ e.appendChild(document.createTextNode(k + ':' + v + ' '));
230
+ var i = document.createElement('i');
231
+ i.setAttribute('class', 'fab fa-' + v);
232
+ e.appendChild(i);
233
+ return e;
234
+ }
235
+ }
236
+
237
+ function default_node_meta_decorator(k, value, nodeMetaTags, serviceMetaIfAny) {
238
+ if (httpRegexp.test(value)) {
239
+ var e = document.createElement('a');
240
+ e.setAttribute('href', value);
241
+ e.setAttribute('class', 'lookup');
242
+ e.appendChild(document.createTextNode(k + ':'+ value));
243
+ var metaTag = document.createElement('span');
244
+ metaTag.setAttribute('class', 'badge badge-secondary mx-1');
245
+ metaTag.appendChild(e);
246
+ return metaTag;
247
+ } else {
248
+ var metaTag = document.createElement('span');
249
+ metaTag.setAttribute('class', 'badge badge-primary mx-1 lookup');
250
+ metaTag.appendChild(document.createTextNode(k + ':' + value));
251
+ return metaTag;
252
+ }
253
+ }
254
+
255
+ function find_decorator(k, v) {
256
+ const nd = node_meta_decorators[k];
257
+ if (nd != null) {
258
+ return nd;
259
+ } else {
260
+ return default_node_meta_decorator;
261
+ }
262
+ }
263
+
264
+ function nodeMetaGenerator(nodeMetaTags, serviceMetaIfAny) {
265
+ var allMeta = document.createElement('div');
266
+ var metaTags = document.createElement('div');
267
+ metaTags.setAttribute('title', 'Node Meta')
268
+ metaTags.className = 'node-meta';
269
+ for (var tagKey in nodeMetaTags) {
270
+ const tagValue = nodeMetaTags[tagKey];
271
+ if (tagValue == ''){
272
+ continue;
273
+ }
274
+ const decorator = find_decorator(tagKey, tagValue);
275
+ var clazz = 'badge badge-primary mx-1 lookup';
276
+ const elem = decorator(tagKey, tagValue, nodeMetaTags, serviceMetaIfAny);
277
+ metaTags.appendChild(elem);
278
+ }
279
+ allMeta.appendChild(metaTags);
280
+ return allMeta;
281
+ }
282
+
226
283
  /**
227
284
  * navBarDecorator is called to modify to modify naviguation bar of all UI pages.
228
285
  * it receives the nav bar div
@@ -122,22 +122,6 @@ function nodeAddressGenator(nodeaddr) {
122
122
  return htmlAddress;
123
123
  }
124
124
 
125
- function nodeMetaGenerator(nodeMetaTags) {
126
- var metaTags = document.createElement('div');
127
- metaTags.setAttribute('title', 'Node Meta')
128
- metaTags.className = 'node-meta';
129
- for (var tagKey in nodeMetaTags) {
130
- if (!nodeMetaTags[tagKey]) {
131
- continue;
132
- }
133
- var metaTag = document.createElement('span');
134
- metaTag.setAttribute('class', 'badge badge-primary mx-1 lookup');
135
- metaTag.appendChild(document.createTextNode(tagKey + ':' + nodeMetaTags[tagKey]));
136
- metaTags.appendChild(metaTag);
137
- }
138
- return metaTags;
139
- }
140
-
141
125
  function tagsGenerator(instanceTags) {
142
126
  var tags = document.createElement('div');
143
127
 
@@ -1,4 +1,6 @@
1
1
  module ConsulTimeline
2
+ # Node of a ringbuffer
3
+ # This contains entries to next and previous elemens as well as the value
2
4
  class RingBufferNode
3
5
  attr_reader :prev, :value, :next
4
6
  attr_writer :prev, :next, :value
@@ -28,10 +30,15 @@ module ConsulTimeline
28
30
  "[prev=#{@prev.object_id}, next=#{@next.object_id}, value=#{@value}]"
29
31
  end
30
32
  end
33
+ # A ringbuffer that supports inserting out of order elements at the right place
34
+ # This is needed as Consul might notify us from changes in any order, so we
35
+ # might receive a notification t-2 AFTER t-1, at t.
36
+ # The ringbuffer will discard old elements so it keeps the "n" most recent elements only.
31
37
  class SortedRingBuffer
32
38
  include Enumerable
33
39
  def initialize(max_size, sort_func)
34
40
  raise "Invalid size #{max_size}" unless max_size.positive?
41
+
35
42
  @head = RingBufferNode.new(nil, nil, nil)
36
43
  @sort_func = sort_func
37
44
  @tail = @head
@@ -43,11 +50,11 @@ module ConsulTimeline
43
50
 
44
51
  def push(obj)
45
52
  return unless obj
53
+
46
54
  cur = @tail
47
55
  raise "No head.next found in #{@head}" unless @head.next
48
- while cur && cur.value && @sort_func.call(cur.value, obj).positive?
49
- cur = cur.prev
50
- end
56
+
57
+ cur = cur.prev while cur&.value && @sort_func.call(cur.value, obj).positive?
51
58
  if cur.nil?
52
59
  # The value we try to insert is before @head
53
60
  # no need to do anything
@@ -64,6 +71,7 @@ module ConsulTimeline
64
71
 
65
72
  def each
66
73
  return enum_for(:each) unless block_given? # Sparkling magic!
74
+
67
75
  cur = @head
68
76
  until cur.nil?
69
77
  yield cur.value if cur.value
@@ -103,5 +111,6 @@ if ARGV.count.positive? && ARGV[0] == 'debug'
103
111
  ringbuff.push 99_999_999_999_999
104
112
  arr = ringbuff.to_a
105
113
  raise "OOPS wrong size := #{arr.count} instead of #{size}" unless arr.count == size
114
+
106
115
  STDOUT.puts JSON.generate(arr)
107
116
  end
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.23.0
4
+ version: 1.24.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: 2020-02-18 00:00:00.000000000 Z
11
+ date: 2020-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: em-http-request
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - '='
116
116
  - !ruby/object:Gem::Version
117
- version: 0.49.0
117
+ version: 0.80.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - '='
123
123
  - !ruby/object:Gem::Version
124
- version: 0.49.0
124
+ version: 0.80.0
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: rubocop-junit-formatter
127
127
  requirement: !ruby/object:Gem::Requirement