consul-templaterb 1.26.1 → 1.27.2

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: 91552da1b0e2061c69095c958665896efc6de84ae6c5374a1b66d1c9550a3297
4
- data.tar.gz: 8bdf67de550a4e2cb7d621ccad117e1e0841b3a0a49bcefb5c914e63daeddb41
3
+ metadata.gz: a67e2df58bcd5c322789984815ff60bd95e50fc047cb20c929a67ca29c07f553
4
+ data.tar.gz: c4df4357ae2852ad487eb90336a0d2d7d380767d51d05e02e7b452d29acda9c7
5
5
  SHA512:
6
- metadata.gz: 24a9e291106b8e32ddef2bdc3d5e87ded36122c84dd1f0547fb034410d606609dd0041d011cd4eeb58da7b38a579888fb5901e877814d49926ef7a0722020d15
7
- data.tar.gz: d3b1b5578cfdc3ee7c594916fc53e22692506d1bc0faaafd91ca21891665732528e95a384766548c90c75bcec0b515f856d1236ffbd18572911363d6ab8f836b
6
+ metadata.gz: 723beb9fca614d1675e50cc1516f37c4aad93815a1725abcdecb33c8bf0aa5e399449f8c3826a14eac8705aa8cb354aa1034796cf68b46304624f958c1b2ba90
7
+ data.tar.gz: 379cb1ca86d8edc44c68694f70b6bd7c1373b8b707f26bef3459379554f94fa12f27653354dc52647eadbd2cf5b0df7cec7f01bcb457e7ed159b7396709ce00d
@@ -7,28 +7,28 @@ Layout/LineLength:
7
7
  Max: 175
8
8
 
9
9
  Metrics/AbcSize:
10
- Max: 82
10
+ Max: 87
11
11
 
12
12
  Metrics/BlockLength:
13
- Max: 160
13
+ Max: 182
14
14
 
15
15
  Metrics/BlockNesting:
16
16
  Max: 4
17
17
 
18
18
  Metrics/ClassLength:
19
- Max: 275
19
+ Max: 285
20
20
 
21
21
  Metrics/CyclomaticComplexity:
22
- Max: 20
22
+ Max: 21
23
23
 
24
24
  Metrics/MethodLength:
25
- Max: 65
25
+ Max: 68
26
26
 
27
27
  Metrics/ParameterLists:
28
- Max: 14
28
+ Max: 18
29
29
 
30
30
  Metrics/PerceivedComplexity:
31
- Max: 23
31
+ Max: 24
32
32
 
33
33
  # We use `dc` as a parameter in many methods
34
34
  Naming/MethodParameterName:
@@ -1,13 +1,13 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.4.9
4
- - 2.5.7
5
- - 2.6.5
6
- - 2.7.0
3
+ - 2.4.10
4
+ - 2.5.8
5
+ - 2.6.6
6
+ - 2.7.1
7
7
  jobs:
8
8
  include:
9
9
  - stage: Gem release
10
- rvm: 2.5.7
10
+ rvm: 2.5.8
11
11
  script: echo "Publishing consul-templaterb on rubygems.org ..."
12
12
  deploy:
13
13
  provider: rubygems
@@ -2,6 +2,43 @@
2
2
 
3
3
  ## (UNRELEASED)
4
4
 
5
+ ## 1.27.2 (Sept 4, 2020)
6
+
7
+ IMPROVEMENTS:
8
+
9
+ * Consul-UI now supports navigation between nodes and services in both ways
10
+
11
+ ## 1.27.1 (July 28, 2020)
12
+
13
+ BUGIX:
14
+
15
+ * Fix collision in JSON queries when using payload in requests #68
16
+
17
+ ## 1.27.0 (June 5, 2020)
18
+
19
+ NEW FEATURES:
20
+
21
+ * For Consul 1.7+, now support `checks_in_state(check_state, dc: nil, [agent: consul_agent_address])`,
22
+ fixes feature [#65](https://github.com/criteo/consul-templaterb/issues/65)
23
+ * New options to support/disable TLS validation thanks to [@jeromegn](https://github.com/jeromegn)
24
+ [#66](https://github.com/criteo/consul-templaterb/pull/66)
25
+
26
+ ## 1.26.3 (April 15, 2020)
27
+
28
+ BUGFIX:
29
+
30
+ * Removed all Criteo specific decorators from Consul-UI
31
+
32
+ ## 1.26.2 (April 15, 2020)
33
+
34
+ BUGFIX:
35
+
36
+ * Fixed broken Dockerfile (was missing the new `decorator.js.erb` file). Fixes #61 (Thanks to @ simongareste)
37
+
38
+ NEW FEATURES:
39
+
40
+ * Added `consul_members_count` metric in [samples/metrics.erb](samples/metrics.erb)
41
+
5
42
  ## 1.26.1 (March 27, 2020)
6
43
 
7
44
  BUGFIX:
data/Dockerfile CHANGED
@@ -12,4 +12,4 @@ ENV LANG C.UTF-8
12
12
  ENV CONSUL_HTTP_ADDR http://consul-relay.service.consul.preprod.crto.in:8500
13
13
 
14
14
  ENTRYPOINT ["/usr/local/bin/bundle", "exec", "consul-templaterb"]
15
- CMD ["--template", "samples/consul-ui/consul-keys-ui.html.erb", "--template", "samples/consul-ui/consul-nodes-ui.html.erb", "--template", "samples/consul-ui/consul-services-ui.html.erb", "--template", "samples/consul-ui/consul-timeline-ui.html.erb", "--template", "samples/consul-ui/consul_keys.json.erb", "--template", "samples/consul-ui/consul_nodes.json.erb", "--template", "samples/consul-ui/consul_services.json.erb", "--template", "samples/consul-ui/timeline.json.erb", "--template", "samples/consul-ui/consul-services-ui.html.erb:samples/consul-ui/index.html:touch samples/consul-ui/ready", "--sig-reload=NONE", "--exec=nginx -c /usr/src/app/docker-nginx-conf/nginx.conf"]
15
+ CMD ["--template", "samples/consul-ui/consul-keys-ui.html.erb", "--template", "samples/consul-ui/decorators.js.erb", "--template", "samples/consul-ui/consul-nodes-ui.html.erb", "--template", "samples/consul-ui/consul-services-ui.html.erb", "--template", "samples/consul-ui/consul-timeline-ui.html.erb", "--template", "samples/consul-ui/consul_keys.json.erb", "--template", "samples/consul-ui/consul_nodes.json.erb", "--template", "samples/consul-ui/consul_services.json.erb", "--template", "samples/consul-ui/timeline.json.erb", "--template", "samples/consul-ui/consul-services-ui.html.erb:samples/consul-ui/index.html:touch samples/consul-ui/ready", "--sig-reload=NONE", "--exec=nginx -c /usr/src/app/docker-nginx-conf/nginx.conf"]
data/README.md CHANGED
@@ -332,7 +332,7 @@ Please consult [CHANGELOG.md](CHANGELOG.md) for fixed bugs.
332
332
 
333
333
  ## TODO
334
334
 
335
- * [x] Hashi's Vault support (EXPERIMENTAL)
335
+ * [x] Hashi's Vault support
336
336
  * [ ] Implement automatic dynamic rate limit
337
337
  * [x] More samples: apache, nginx, a full website displaying consul information...
338
338
  * [x] Optimize rendering speed at start-up: an iteration is done every second by default, but it would be possible to speed
@@ -388,6 +388,15 @@ name or its ID. If DC is specified, will lookup for given node in another datace
388
388
 
389
389
  [Find all the checks](https://www.consul.io/api/health.html#list-checks-for-service) of a given service.
390
390
 
391
+ ## def checks_in_state(check_state, dc: nil, [agent: consul_agent_address])
392
+
393
+ [Find all the checks in a given state](https://www.consul.io/api-docs/health#list-checks-in-state) in the whole cluster.
394
+
395
+ The filter check_state must be one of any|critical|warning|passing.
396
+
397
+ Warning: this endpoint might be very frequently updated in a
398
+ large cluster if you are using `any` value. This endpoint is supported with Consul 1.7+.
399
+
391
400
  ## kv(name, [dc: nil], [keys: false], [recurse: false], [agent: consul_agent_address])
392
401
 
393
402
  [Read keys from KV Store](https://www.consul.io/api/kv.html#read-key). It can be used for both listing the keys and
@@ -28,6 +28,9 @@ options = {
28
28
  },
29
29
  base_url: ENV['VAULT_ADDR'] || 'http://localhost:8200',
30
30
  token: ENV['VAULT_TOKEN'] || nil,
31
+ tls_cert_chain: ENV['VAULT_CLIENT_CERT'] || nil,
32
+ tls_private_key: ENV['VAULT_CLIENT_KEY'] || nil,
33
+ tls_verify_peer: true,
31
34
  max_consecutive_errors_on_endpoint: 10, # Stop program after n consecutive failures on same endpoint
32
35
  fail_fast_errors: nil, # fail fast the program if endpoint was never success
33
36
  token_renew: true,
@@ -48,6 +51,9 @@ options = {
48
51
  },
49
52
  base_url: ENV['CONSUL_HTTP_ADDR'] || 'http://localhost:8500',
50
53
  token: ENV['CONSUL_HTTP_TOKEN'] || nil,
54
+ tls_cert_chain: ENV['CONSUL_CLIENT_CERT'] || nil,
55
+ tls_private_key: ENV['CONSUL_CLIENT_KEY'] || nil,
56
+ tls_verify_peer: true,
51
57
  max_consecutive_errors_on_endpoint: 10, # Stop program after n consecutive failures on same endpoint
52
58
  fail_fast_errors: nil, # fail fast the program if endpoint was never success
53
59
  retry_duration: 10, # On error, retry after n seconds
@@ -122,6 +128,18 @@ optparse = OptionParser.new do |opts|
122
128
  options[:consul][:base_url] = consul_url
123
129
  end
124
130
 
131
+ opts.on('--consul-cert-chain=<path/to/cert_chain>', String, 'Path to Consul TLS client certificate chain to use') do |consul_client_cert|
132
+ options[:consul][:tls_cert_chain] = consul_client_cert
133
+ end
134
+
135
+ opts.on('--consul-private-key=<path/to/private_key>', String, 'Path to Consul TLS client private key to use') do |consul_client_key|
136
+ options[:consul][:tls_private_key] = consul_client_key
137
+ end
138
+
139
+ opts.on('--skip-consul-verify-tls', 'Skip verifying Consul TLS via certificate authority (DANGEROUS)') do
140
+ options[:consul][:tls_verify_peer] = false
141
+ end
142
+
125
143
  opts.on('-l', '--log-level=<log_level>', String, "Log level, default=info, any of #{::Consul::Async::Debug.levels.join('|')}") do |log_level|
126
144
  ::Consul::Async::Debug.level = log_level
127
145
  end
@@ -134,6 +152,18 @@ optparse = OptionParser.new do |opts|
134
152
  options[:vault][:base_url] = vault_url
135
153
  end
136
154
 
155
+ opts.on('--vault-cert-chain=<path/to/cert_chain>', String, 'Path to Vault TLS client certificate chain to use') do |vault_client_cert|
156
+ options[:vault][:tls_cert_chain] = vault_client_cert
157
+ end
158
+
159
+ opts.on('--vault-private-key=<path/to/private_key>', String, 'Path to Vault TLS client private key to use') do |vault_client_key|
160
+ options[:vault][:tls_private_key] = vault_client_key
161
+ end
162
+
163
+ opts.on('--skip-vault-verify-tls', 'Skip verifying Vault TLS via certificate authority (DANGEROUS)') do
164
+ options[:vault][:tls_verify_peer] = false
165
+ end
166
+
137
167
  opts.on('-T', '--vault-token=<token>', String, 'Token used to authenticate against vault.') do |vault_token|
138
168
  options[:vault][:token] = vault_token
139
169
  end
@@ -9,7 +9,7 @@ module Consul
9
9
  class ConsulConfiguration
10
10
  attr_reader :base_url, :token, :retry_duration, :min_duration, :wait_duration, :max_retry_duration, :retry_on_non_diff,
11
11
  :missing_index_retry_time_on_diff, :missing_index_retry_time_on_unchanged, :debug, :enable_gzip_compression,
12
- :fail_fast_errors, :max_consecutive_errors_on_endpoint
12
+ :fail_fast_errors, :max_consecutive_errors_on_endpoint, :tls_cert_chain, :tls_private_key, :tls_verify_peer
13
13
  def initialize(base_url: 'http://localhost:8500',
14
14
  debug: { network: false },
15
15
  token: nil,
@@ -23,7 +23,10 @@ module Consul
23
23
  enable_gzip_compression: true,
24
24
  paths: {},
25
25
  max_consecutive_errors_on_endpoint: 10,
26
- fail_fast_errors: 1)
26
+ fail_fast_errors: 1,
27
+ tls_cert_chain: nil,
28
+ tls_private_key: nil,
29
+ tls_verify_peer: true)
27
30
  @base_url = base_url
28
31
  @token = token
29
32
  @debug = debug
@@ -38,6 +41,9 @@ module Consul
38
41
  @paths = paths
39
42
  @max_consecutive_errors_on_endpoint = max_consecutive_errors_on_endpoint
40
43
  @fail_fast_errors = fail_fast_errors
44
+ @tls_cert_chain = tls_cert_chain
45
+ @tls_private_key = tls_private_key
46
+ @tls_verify_peer = tls_verify_peer
41
47
  end
42
48
 
43
49
  def ch(path, symbol)
@@ -71,7 +77,10 @@ module Consul
71
77
  enable_gzip_compression: enable_gzip_compression,
72
78
  paths: @paths,
73
79
  max_consecutive_errors_on_endpoint: @max_consecutive_errors_on_endpoint,
74
- fail_fast_errors: @fail_fast_errors)
80
+ fail_fast_errors: @fail_fast_errors,
81
+ tls_cert_chain: ch(path, :tls_cert_chain),
82
+ tls_private_key: ch(path, :tls_private_key),
83
+ tls_verify_peer: ch(path, :tls_verify_peer))
75
84
  end
76
85
  end
77
86
 
@@ -233,6 +242,13 @@ module Consul
233
242
  connect_timeout: 5, # default connection setup timeout
234
243
  inactivity_timeout: conf.wait_duration + 1 + (conf.wait_duration / 16) # default connection inactivity (post-setup) timeout
235
244
  }
245
+ unless conf.tls_cert_chain.nil?
246
+ options[:tls] = {
247
+ cert_chain_file: conf.tls_cert_chain,
248
+ private_key_file: conf.tls_private_key,
249
+ verify_peer: conf.tls_verify_peer
250
+ }
251
+ end
236
252
  connection = {
237
253
  conn: EventMachine::HttpRequest.new(conf.base_url, options)
238
254
  }
@@ -31,7 +31,8 @@ module Consul
31
31
 
32
32
  def as_json(url, default_value, refresh_delay_secs: 10, **opts)
33
33
  conf = JSONConfiguration.new(url: url, min_duration: refresh_delay_secs, retry_on_non_diff: refresh_delay_secs, **opts)
34
- @endp_manager.create_if_missing(url, {}) do
34
+ endpoint_id = url + opts.to_json
35
+ @endp_manager.create_if_missing(url, {}, endpoint_id: endpoint_id) do
35
36
  if default_value.is_a?(Array)
36
37
  ConsulTemplateJSONArray.new(JSONEndpoint.new(conf, url, default_value))
37
38
  else
@@ -161,6 +162,18 @@ module Consul
161
162
  create_if_missing(path, query_params, agent: agent) { ConsulTemplateChecks.new(ConsulEndpoint.new(consul_conf, path, true, query_params, '[]', agent)) }
162
163
  end
163
164
 
165
+ # https://www.consul.io/api-docs/health#list-checks-in-state
166
+ # Supported in Consul 1.7+
167
+ def checks_in_state(check_state, dc: nil, agent: nil)
168
+ valid_checks_states = %w[any critical passing warning]
169
+ raise "checks_in_state('#{check_state}'...) must be one of #{valid_checks_states}" unless valid_checks_states.include?(check_state)
170
+
171
+ path = "/v1/health/state/#{check_state}"
172
+ query_params = {}
173
+ query_params[:dc] = dc if dc
174
+ create_if_missing(path, query_params, agent: agent) { ConsulTemplateChecks.new(ConsulEndpoint.new(consul_conf, path, true, query_params, '[]', agent)) }
175
+ end
176
+
164
177
  # https://www.consul.io/api/catalog.html#list-nodes
165
178
  def nodes(dc: nil, agent: nil)
166
179
  path = '/v1/catalog/nodes'
@@ -382,16 +395,21 @@ module Consul
382
395
  VaultEndpoint.new(vault_conf, path, :POST, {}, {})
383
396
  end
384
397
 
385
- def create_if_missing(path, query_params, fail_fast_errors: @fail_fast_errors, max_consecutive_errors_on_endpoint: @max_consecutive_errors_on_endpoint, agent: nil)
386
- fqdn = path.dup
387
- query_params.each_pair do |k, v|
388
- fqdn = "#{agent}#{fqdn}&#{k}=#{v}"
389
- end
390
- tpl = @endpoints[fqdn]
398
+ def create_if_missing(path, query_params, fail_fast_errors: @fail_fast_errors,
399
+ max_consecutive_errors_on_endpoint: @max_consecutive_errors_on_endpoint,
400
+ agent: nil, endpoint_id: nil)
401
+ endpoint_id ||= begin
402
+ fqdn = path.dup
403
+ query_params.each_pair do |k, v|
404
+ fqdn = "#{agent}#{fqdn}&#{k}=#{v}"
405
+ end
406
+ fqdn
407
+ end
408
+ tpl = @endpoints[endpoint_id]
391
409
  unless tpl
392
410
  tpl = yield
393
411
  ::Consul::Async::Debug.print_debug "path #{path.ljust(64)} #{query_params.inspect}\r"
394
- @endpoints[fqdn] = tpl
412
+ @endpoints[endpoint_id] = tpl
395
413
  tpl.endpoint.on_response do |result|
396
414
  @net_info[:success] += 1
397
415
  @net_info[:bytes_read] += result.data.bytesize
@@ -9,7 +9,7 @@ module Consul
9
9
  class JSONConfiguration
10
10
  attr_reader :url, :retry_duration, :min_duration, :retry_on_non_diff,
11
11
  :debug, :enable_gzip_compression, :request_method, :json_body,
12
- :headers
12
+ :headers, :tls_cert_chain, :tls_private_key, :tls_verify_peer
13
13
  def initialize(url:,
14
14
  debug: { network: false },
15
15
  retry_duration: 10,
@@ -18,7 +18,10 @@ module Consul
18
18
  request_method: :get,
19
19
  json_body: nil,
20
20
  headers: {},
21
- enable_gzip_compression: true)
21
+ enable_gzip_compression: true,
22
+ tls_cert_chain: nil,
23
+ tls_private_key: nil,
24
+ tls_verify_peer: true)
22
25
  @url = url
23
26
  @debug = debug
24
27
  @enable_gzip_compression = enable_gzip_compression
@@ -28,6 +31,9 @@ module Consul
28
31
  @request_method = request_method
29
32
  @json_body = json_body
30
33
  @headers = headers
34
+ @tls_cert_chain = tls_cert_chain
35
+ @tls_private_key = tls_private_key
36
+ @tls_verify_peer = tls_verify_peer
31
37
  end
32
38
 
33
39
  def create(_url)
@@ -181,6 +187,13 @@ module Consul
181
187
  connect_timeout: 5, # default connection setup timeout
182
188
  inactivity_timeout: 60 # default connection inactivity (post-setup) timeout
183
189
  }
190
+ unless conf.tls_cert_chain.nil?
191
+ options[:tls] = {
192
+ cert_chain_file: conf.tls_cert_chain,
193
+ private_key_file: conf.tls_private_key,
194
+ verify_peer: conf.tls_verify_peer
195
+ }
196
+ end
184
197
  connection = {
185
198
  conn: EventMachine::HttpRequest.new(conf.url, options)
186
199
  }
@@ -10,7 +10,8 @@ module Consul
10
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
- :lease_duration_factor, :debug, :max_consecutive_errors_on_endpoint, :fail_fast_errors
13
+ :lease_duration_factor, :debug, :max_consecutive_errors_on_endpoint, :fail_fast_errors, :tls_cert_chain, :tls_private_key,
14
+ :tls_verify_peer
14
15
 
15
16
  def initialize(base_url: 'http://localhost:8200',
16
17
  debug: { network: false },
@@ -22,7 +23,10 @@ module Consul
22
23
  max_retry_duration: 600,
23
24
  paths: {},
24
25
  max_consecutive_errors_on_endpoint: 10,
25
- fail_fast_errors: false)
26
+ fail_fast_errors: false,
27
+ tls_cert_chain: nil,
28
+ tls_private_key: nil,
29
+ tls_verify_peer: true)
26
30
  @base_url = base_url
27
31
  @token_renew = token_renew
28
32
  @debug = debug
@@ -34,6 +38,9 @@ module Consul
34
38
  @token = token
35
39
  @max_consecutive_errors_on_endpoint = max_consecutive_errors_on_endpoint
36
40
  @fail_fast_errors = fail_fast_errors
41
+ @tls_cert_chain = tls_cert_chain
42
+ @tls_private_key = tls_private_key
43
+ @tls_verify_peer = tls_verify_peer
37
44
  end
38
45
 
39
46
  def ch(path, symbol)
@@ -226,6 +233,13 @@ module Consul
226
233
  connect_timeout: 5, # default connection setup timeout
227
234
  inactivity_timeout: 1 # default connection inactivity (post-setup) timeout
228
235
  }
236
+ unless conf.tls_cert_chain.nil?
237
+ options[:tls] = {
238
+ cert_chain_file: conf.tls_cert_chain,
239
+ private_key_file: conf.tls_private_key,
240
+ verify_peer: conf.tls_verify_peer
241
+ }
242
+ end
229
243
  connection = EventMachine::HttpRequest.new(conf.base_url, options)
230
244
  cb = proc do |_|
231
245
  http = connection.send(http_method.downcase, build_request) # Under the hood: c.send('get', {stuff}) === c.get({stuff})
@@ -1,5 +1,5 @@
1
1
  module Consul
2
2
  module Async
3
- VERSION = '1.26.1'.freeze
3
+ VERSION = '1.27.2'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,13 @@
1
+ <%=
2
+ # This sample displays checks for the whole cluster
3
+ # in warning or critical state
4
+ # API available with Consul 1.7+
5
+ res = []
6
+ checks_in_state('warning').each do |c|
7
+ res << c
8
+ end
9
+ checks_in_state('critical').each do |c|
10
+ res << c
11
+ end
12
+ YAML.dump({'warning_or_critical_checks' => res})
13
+ %>
@@ -50,6 +50,25 @@ The content is statically created, so you can serve it using any HTTP server ver
50
50
  For development, you might use `python -m SimpleHTTPServer` in order to server the result
51
51
  on your browser.
52
52
 
53
+ ### Running it in production
54
+
55
+ Whatever your solution, be sure to have a index.html, so read next below on
56
+ how generating an index.html.
57
+
58
+ #### Running with web server
59
+
60
+ You can run it with your favoite web server, at Criteo we run it with nginx
61
+ which handles nicely cache and offer good performance.
62
+
63
+ In that case, consul-templaterb can start nginx with `--exec` or you can run it
64
+ as a daemon, in which case, consul-templaterb only generate the files.
65
+
66
+ #### Run it in Consul
67
+
68
+ You can run consul with `-ui-dir=/path/to/directory/of/consul-ui`, in such case
69
+ reaching consul on poort 8500 will redirect to the /ui/ path, displaying the UI
70
+ of your choice on http://consul-agent.example.org:8500/ui/.
71
+
53
72
  ### Generating index.html
54
73
 
55
74
  By default, the command `consul-templaterb -c http://localhost:8500 samples/consul-ui/*.erb``
@@ -15,6 +15,10 @@
15
15
  content: '⚠ ';
16
16
  }
17
17
 
18
+ .btn-outline-success a:hover, .btn-outline-warning a:hover, .btn-outline-danger a:hover{
19
+ color: #fff;
20
+ }
21
+
18
22
  #service-wrapper, #instances-wrapper, #keys-wrapper {
19
23
  overflow-y: scroll;
20
24
  border-top-left-radius: 0px;
@@ -1,18 +1,5 @@
1
- // Utilities
1
+ // Use this file to configure custom decorators for your Consul-UI
2
2
  var httpRegexp = new RegExp('^http[s]?://[^ ]+$');
3
- var dc = "<%= ENV['CRITEO_DC'] || 'par'%>";
4
- var env = "<%= ENV['CRITEO_ENV'] || 'preprod'%>";
5
-
6
- var availability_url = 'https://grafana.crto.in/d/xFX5gCnWz/service-availability?var-datacenter=' + dc + '&var-service=';
7
- if (env == 'preprod') {
8
- availability_url = 'https://grafana.preprod.crto.in/d/E0ANGjnZz/service-availability?var-datacenter=' + dc + '&var-service=';
9
- }
10
- var swagger_url = 'https://swaggercatalogapp.' + dc + '.' + env + '.crto.in/explore/swagger?key=';
11
- var slack_url = 'https://criteo.slack.com/app_redirect?channel=';
12
-
13
- var rackguru_url = 'https://rackguru.' + env + '.crto.in/serial/';
14
-
15
- var asapi_url = 'https://idm.' + env + '.crto.in/tool/multiGroupInfo/'
16
3
 
17
4
  function url_decorator(key, value) {
18
5
  var e = document.createElement('a');
@@ -26,27 +13,7 @@ function usefullLinksGenerator(instance, serviceName, node_meta_info) {
26
13
  top.className = 'instance-links';
27
14
 
28
15
  var usefullLinks = [
29
- {
30
- title: "Trigger security scan",
31
- iconClassName: "fas fa-shield-alt",
32
- href: "https://security.crto.in/#/scan/?ip=" + instance.addr
33
- },
34
- {
35
- title: "Availability Graph",
36
- iconClassName: "fas fa-chart-area",
37
- href: availability_url + serviceName
38
- },
39
16
  ];
40
- if (node_meta_info!= null) {
41
- var serial = node_meta_info['serial_number'];
42
- if (serial != null) {
43
- usefullLinks.push({
44
- title: "RackGuru",
45
- iconClassName: "fas fa-server",
46
- href: rackguru_url + serial
47
- });
48
- }
49
- }
50
17
  var first = true;
51
18
  for (let usefullLink of usefullLinks) {
52
19
  link = document.createElement('a');
@@ -134,19 +101,6 @@ function groups_decorator(instance, key, value, serviceName) {
134
101
  return span;
135
102
  }
136
103
 
137
- /**
138
- * Decorates with a slack channel link
139
- */
140
- function slack_channel(instance, key, value, serviceName) {
141
- var sName = value;
142
- if (sName.startsWith('#')) {
143
- sName = sName.substring(1);
144
- }
145
- return build_link(slack_url + encodeURIComponent(sName), '#'+ sName);
146
- }
147
-
148
- const start_regexp = /(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z/
149
-
150
104
  function decorateIsoDate(value, formated) {
151
105
  const parsed = Date.parse(formated);
152
106
  if (isNaN(parsed)) {
@@ -163,55 +117,6 @@ function decorateIsoDate(value, formated) {
163
117
  // This is the list of function called
164
118
  // When a service meta is found
165
119
  var registered_decorators = {
166
- alert_availability_slack_channel: slack_channel,
167
- gerrit_repository: function(instance, key, value, serviceName) {
168
- return build_link('https://review.crto.in/#/q/' + value, value);
169
- },
170
- version: function(instance, key, value, serviceName) {
171
- var asInt = parseInt(value);
172
- if (asInt < 10000) {
173
- return document.createTextNode(value);
174
- }
175
- if (instance.sMeta['CRITEO_APP_POOL'] != null) {
176
- return build_link("https://devtools.crto.in/log.html?moab=cs&im=nb-to&range=100%2C" + asInt, value);
177
- } else {
178
- var tUrl = "https://devtools.crto.in/log.html?moab=j&im=nb-to&range=100%2C" + asInt;
179
- if (instance.sMeta['jvm_artifact'] != null) {
180
- tUrl += '&with-dependencies=true&artifacts=' + encodeURIComponent(instance.sMeta['jvm_artifact']);
181
- }
182
- return build_link(tUrl, value);
183
- }
184
- },
185
- MESOS_TERM_DEBUG_GRANTED_TO: groups_decorator,
186
- OWNERS: groups_decorator,
187
- marathon_app_id: function(instance, key, value, serviceName) {
188
- var app_name = value;
189
- if (app_name[0] != '/') {
190
- app_name = '/' + app_name;
191
- }
192
- if (instance.sMeta && instance.sMeta['marathon_ui']) {
193
- return build_link(instance.sMeta['marathon_ui'] + '/#/apps/' + encodeURIComponent(app_name), value);
194
- } else {
195
- // non decorated value
196
- return document.createTextNode(value);
197
- }
198
- },
199
- marathon_app_version: function(instance, key, value, serviceName) {
200
- return decorateIsoDate(value, value);
201
- },
202
- slack_channel: slack_channel,
203
- start: function(instance, key, value, serviceName) {
204
- const reg = start_regexp.exec(value);
205
- if (reg != null) {
206
- var formated = reg[1] + '-' + reg[2] + '-' + reg[3] + 'T' + reg[4] + ':' + reg[5] + ':' + reg[6] + 'Z';
207
- return decorateIsoDate(value, formated);
208
- } else {
209
- return document.createTextNode(value);
210
- }
211
- },
212
- swagger_key: function(instance, key, value, serviceName) {
213
- return build_link(swagger_url + encodeURIComponent(value), value);
214
- },
215
120
  }
216
121
 
217
122
  function service_meta_semantics_decorator(instance, key, value, serviceName) {
@@ -286,85 +191,11 @@ function nodeMetaGenerator(nodeMetaTags, serviceMetaIfAny) {
286
191
  * it does not have to return anything.
287
192
  */
288
193
  function navBarDecorator(navbar) {
289
- if (typeof consulManager === 'undefined') {
290
- // Timepicker is not supported on this page
291
- return;
292
- }
293
- var timepicker_container = document.createElement('div');
294
- timepicker_container.innerHTML = `
295
- <div class="row">
296
- <button type="button" disabled class="btn btn-secondary" data-toggle="tooltip" data-html="true" title="Data returned is the closest available to request" id="currently-displayed-data-date">
297
- Pick a date to see data from the past
298
- </button>
299
- <div class="col-sm-14">
300
- <div class="form-group">
301
- <div class="input-group date" id="datetimepicker1" data-target-input="nearest">
302
- <input type="text" class="form-control datetimepicker-input" data-target="#datetimepicker1"/>
303
- <div class="input-group-append" data-target="#datetimepicker1" data-toggle="datetimepicker">
304
- <div class="input-group-text"><i class="fa fa-calendar"></i></div>
305
- </div>
306
- </div>
307
- </div>
308
- </div>
309
- </div>
310
- `;
311
-
312
- navbar.appendChild(timepicker_container);
313
- var script= document.createElement('script');
314
- script.type='text/javascript';
315
- // TODO(g.seux): extract code to another file and set "source" on script element
316
- script.innerHTML = `
317
- $('#datetimepicker1').datetimepicker({
318
- format: 'DD/MM/YYYY HH:mm:ss Z', // default format does not allow to select seconds
319
- sideBySide: true, // display date+time on the same widget
320
- useCurrent: true // by default, select current date
321
- });
322
- $("#datetimepicker1").on("change.datetimepicker", function (e) {
323
- console.log("Will fetch data from date " + e.date);
324
- console.log("will clean existing data");
325
- consulManager.clean().then(function(result) {
326
- switch(consulManager.constructor.name) {
327
- case "ConsulServiceManager":
328
- backup_type = 'consul_services';
329
- break;
330
- case "ConsulKeysManager":
331
- backup_type = 'consul_keys'
332
- break;
333
- case "ConsulNodesManager":
334
- backup_type = 'consul_nodes'
335
- break;
336
- default:
337
- console.log("Unknown " + consulManager.constructor.name + " type");
338
- }
339
- if (e.date) {
340
- console.log("Will replace data by closest snapshot to " + e.date);
341
- var target_url = "https://consul-info-timeline-history.<%= ENV['CRITEO_DC'] %>.<%= ENV['CRITEO_ENV']%>.crto.in/backup/" + backup_type + "/" + e.date / 1000;
342
- } else {
343
- console.log("Will restore to local version");
344
- var target_url = defaultConsulManager.resourceURL;
345
- }
346
- if (typeof(defaultConsulManager) == 'undefined') {
347
- // store first manager to be able to restore it
348
- defaultConsulManager = consulManager
349
- }
350
- consulManager = new consulManager.constructor(target_url);
351
- });
352
- });
353
- `;
354
- navbar.appendChild(script);
355
194
  }
356
195
 
357
-
358
196
  /**
359
197
  * fetchedResponseDecorator is called with http response when a resource is fetched by any instance of ConsulUIManager
360
198
  * it does not have to return anything.
361
199
  */
362
200
  async function fetchedResponseDecorator(httpResponse) {
363
- const data_date = await httpResponse.headers.get('X-Consul-Snapshot-Timestamp');
364
- current_date_display = $('#currently-displayed-data-date');
365
- if (data_date > 0) {
366
- current_date_display.html("Data is a snapshot from: " + moment.unix(data_date).format('DD/MM/YYYY HH:mm:ss Z'));
367
- } else {
368
- current_date_display.html("Pick a date to see data from the past");
369
- }
370
- }
201
+ }
@@ -111,7 +111,7 @@ class NodeMainSelector extends MainSelector {
111
111
  nodesChecks.appendChild(checksStatusGenerator(node, node['Node']['Name']));
112
112
  content.appendChild(nodesChecks);
113
113
 
114
- content.appendChild(servicesGenerator(node['Service']));
114
+ content.appendChild(servicesGenerator(node['Service'], node));
115
115
  content.appendChild(tagsGenerator(getTagsNode(node)));
116
116
 
117
117
  sidebar.setAttribute('status', state);
@@ -63,7 +63,7 @@ function nodeState(checks) {
63
63
 
64
64
  supported_protocols = ['https', 'http', 'sftp', 'ftp', 'ssh', 'telnet']
65
65
 
66
- function serviceTitleGenerator(instance) {
66
+ function serviceTitleGenerator(instance, serviceName) {
67
67
  var protocol = null;
68
68
  for (i in supported_protocols) {
69
69
  var protoc = supported_protocols[i]
@@ -74,10 +74,6 @@ function serviceTitleGenerator(instance) {
74
74
  }
75
75
 
76
76
  var htmlTitle = document.createElement('h5');
77
- htmlTitle.setAttribute('title', 'Node Name: ' + instance.name +
78
- '\nAddress: ' + instance.addr +
79
- '\nService ID: ' + instance.id +
80
- '\nService Port: ' + instance.port);
81
77
 
82
78
  htmlTitle.setAttribute('class', 'instance-name');
83
79
  var instanceLink = document.createElement('a');
@@ -91,6 +87,15 @@ function serviceTitleGenerator(instance) {
91
87
  }
92
88
 
93
89
  instanceLink.appendChild(document.createTextNode(instance.name + appendPort));
90
+ const nodeInfo = document.createElement('a');
91
+ nodeInfo.appendChild(document.createTextNode('\u24D8'));
92
+ nodeInfo.setAttribute('title', 'Click to see details of Node: ' + instance.name +
93
+ '\nAddress: ' + instance.addr +
94
+ '\nService ID: ' + instance.id +
95
+ '\nService Port: ' + instance.port);
96
+ nodeInfo.setAttribute('href', 'consul-nodes-ui.html?node_filter=^' + encodeURIComponent(instance.name) + '$');
97
+ htmlTitle.appendChild(nodeInfo);
98
+ htmlTitle.appendChild(document.createTextNode(' '));
94
99
  htmlTitle.appendChild(instanceLink);
95
100
  htmlTitle.appendChild(document.createTextNode(' '));
96
101
  htmlTitle.appendChild(document.createTextNode(instance.addr));
@@ -216,24 +221,44 @@ function toCSSClass(state) {
216
221
  return state;
217
222
  }
218
223
 
219
- function servicesGenerator(instanceServices) {
220
- var services = document.createElement('div');
221
- services.className = 'instance-services';
222
- services.appendChild(document.createTextNode("Services: "));
223
- services.appendChild(document.createElement('br'));
224
+ function servicesGenerator(instanceServices, node) {
225
+ var servicesTop = document.createElement('div');
226
+ servicesTop.className = 'instance-services';
227
+ const card = document.createElement('div');
228
+ card.setAttribute('class', 'card');
229
+ const servicesCard = document.createElement('div');
230
+ servicesCard.setAttribute('class', 'card-body');
231
+ const title = document.createElement('h5');
232
+ title.appendChild(document.createTextNode('Services'));
233
+ title.setAttribute('class', 'card-title')
234
+ servicesCard.appendChild(title);
235
+ const services = document.createElement('div');
236
+ servicesCard.appendChild(services);
224
237
  for (var serviceKey in instanceServices) {
225
- var service = document.createElement('a');
226
- var serviceName = instanceServices[serviceKey]['Service']['Service'];
238
+ const serviceGrp = document.createElement('span');
239
+ serviceGrp.setAttribute('class', 'btn btn-sm');
240
+ serviceGrp.classList.add('btn-outline-' + toCSSClass(nodeState(instanceServices[serviceKey]['Checks'])))
241
+ const service = document.createElement('a');
242
+ serviceGrp.appendChild(service);
243
+ const serviceName = instanceServices[serviceKey]['Service']['Service'];
244
+ service.setAttribute('class', 'serviceLink');
245
+ service.setAttribute('href', 'consul-services-ui.html?service=' + encodeURIComponent(serviceName) + '&node_filter=^' + encodeURIComponent(node['Node']['Name'])+'$');
246
+ service.appendChild(document.createTextNode(serviceName));
227
247
  var servicePort = instanceServices[serviceKey]['Service']['Port'];
228
- service.setAttribute('class', 'btn btn-sm m-1 lookup');
229
- service.setAttribute('target', '_blank');
230
- nodeAddr = instanceServices[serviceKey]['Service']['Address'];
231
- service.setAttribute('href', 'http://' + nodeAddr + ':' + servicePort);
232
- service.classList.add('btn-outline-' + toCSSClass(nodeState(instanceServices[serviceKey]['Checks'])))
233
- service.appendChild(document.createTextNode(serviceName + ':' + servicePort));
234
- services.appendChild(service);
248
+ if (servicePort) {
249
+ // Add unbreakable space
250
+ serviceGrp.appendChild(document.createTextNode('\u00A0'));
251
+ const servicePortElem = document.createElement('a');
252
+ servicePortElem.setAttribute('class', 'serviceTargetPort');
253
+ const nodeAddr = instanceServices[serviceKey]['Service']['Address'];
254
+ servicePortElem.setAttribute('href', 'http://' + nodeAddr + ':' + servicePort);
255
+ servicePortElem.appendChild(document.createTextNode(':' + servicePort));
256
+ serviceGrp.appendChild(servicePortElem);
257
+ }
258
+ services.appendChild(serviceGrp);
235
259
  }
236
- return services;
260
+ servicesTop.appendChild(servicesCard);
261
+ return servicesTop;
237
262
  }
238
263
 
239
264
  function checksStatusGenerator(instance, prefix) {
@@ -0,0 +1,17 @@
1
+ <%
2
+ # This example show how to display local time informaition about changes
3
+ # Example of usage to display logs of changes on nodes() endpoint:
4
+ #
5
+ # consul-templaterb --template "display_timestamped_changes.txt.erb:display_timestamped_changes.txt:cat display_timestamped_changes.txt" -l error
6
+ #
7
+ # Would output:
8
+ # Last update: 1588800554 (2020-05-06 21:29:14 UTC), X-Consul-Index: 4345827328
9
+ # Last update: 1588800569 (2020-05-06 21:29:29 UTC), X-Consul-Index: 4345829548
10
+ # Last update: 1588800676 (2020-05-06 21:31:16 UTC), X-Consul-Index: 4345836342
11
+ #
12
+ @my_last_time = Time.now.utc unless @my_last_time
13
+ val = nodes()
14
+ new_idx = val.endpoint.x_consul_index
15
+ @my_last_time = Time.now.utc if @my_last_idx != new_idx
16
+ @my_last_idx = new_idx
17
+ %>Last update: <%= @my_last_time.to_i %> (<%= @my_last_time %>), X-Consul-Index: <%= new_idx %>
@@ -0,0 +1,28 @@
1
+ <%=
2
+ all_catalog = nodes.map{ |n| [n['Node'], n] }.to_h
3
+ all_members = agent_members.sort{ |a,b| a['Name'] <=> b['Name'] }
4
+ members_by_state = {}
5
+ all_members.each do |m|
6
+ state = m.status
7
+ members = members_by_state[state] || 0
8
+ members_by_state[state] = members + 1
9
+ end
10
+ # Find all virtual nodes (eg: k8s-sync)
11
+ catalog_external_sources = nodes.select{|n| (n['Meta']||{})['external-source'] }.map{|n| n['Node']}
12
+ in_catalog_but_not_in_members = all_catalog.keys - all_members.map{ |m| m['Name'] }
13
+ not_virtual_not_in_members = in_catalog_but_not_in_members - catalog_external_sources
14
+ JSON.pretty_generate(
15
+ {
16
+ 'stats' => {
17
+ 'catalog' => all_catalog.count,
18
+ 'catalog_virtual' => in_catalog_but_not_in_members.count,
19
+ 'members' => all_members.count,
20
+ 'members_by_state': members_by_state,
21
+ 'catalog_external_sources' => catalog_external_sources,
22
+ },
23
+ 'errors': {
24
+ 'in_catalog_but_not_in_members' => in_catalog_but_not_in_members,
25
+ 'not_virtual_not_in_members' => not_virtual_not_in_members,
26
+ }
27
+ })
28
+ %>
@@ -21,6 +21,20 @@
21
21
  all_stats = {}
22
22
  service_count = 0
23
23
 
24
+ statuses = agent_members().map {|m| m.status}.reduce({}) do |sum, s|
25
+ v = sum[s] || 0
26
+ sum[s] = v + 1
27
+ sum
28
+ end
29
+ %>
30
+ # HELP consul_members_count A gauge of number of serf members with their count and their status('alive', 'leaving', 'left', 'failed')
31
+ # TYPE consul_members_count gauge
32
+ <%
33
+ statuses.each do |k, v|
34
+ %>
35
+ consul_members_count{serf="lan",status="<%= k %>"} <%= v %><%
36
+ end
37
+
24
38
  services(tag: service_tag_filter).each do |service_name, tags|
25
39
  if !services_blacklist.any? {|r| r.match(service_name)} && (instance_must_tag.nil? || tags.include?(instance_must_tag))
26
40
  service_count += 1
@@ -0,0 +1,56 @@
1
+ # HELP consul_wan_servers_rtt_seconds Min Round trip with other servers of DC
2
+ # TYPE consul_wan_servers_rtt_seconds gauge
3
+ <%
4
+ # This computes the RTT over WAN for all DCs
5
+ results = {'min' => {}, 'max' => {}, 'p50' => {}, 'p90' => {}}
6
+ my_dcname = (agent_self['Config'] || {})['Datacenter']
7
+ my_dc = coordinate.datacenters.select { |d| d['Datacenter'] == my_dcname }.first
8
+
9
+ def percentile(values_sorted, percentile)
10
+ k = (percentile*(values_sorted.length-1)+1).floor - 1
11
+ f = (percentile*(values_sorted.length-1)+1).modulo(1)
12
+ return values_sorted[k] + (f * (values_sorted[k+1] - values_sorted[k]))
13
+ end
14
+
15
+ if my_dc
16
+ from = my_dc['Datacenter']
17
+ coordinate.datacenters.each do |dc_coords|
18
+ min = Float::MAX
19
+ max = 0
20
+ rtts = []
21
+ dc_coords['Coordinates'].each do |s2|
22
+ my_dc['Coordinates'].each do |s1|
23
+ rtt = coordinate.rtt(s1['Coord'], s2['Coord'])
24
+ min = rtt if rtt < min
25
+ max = rtt if rtt > max
26
+ idx = rtts.bsearch_index { |x| x > rtt }
27
+ if idx.nil?
28
+ rtts << rtt
29
+ else
30
+ rtts = rtts.insert(idx, rtt)
31
+ end
32
+ %>
33
+ consul_wan_servers_rtt_seconds{from="<%= from %>",to="<%= dc_coords['Datacenter'] %>",from_node="<%= s1['Node'] %>",to_node="<%= s2['Node'] %>"} <%= rtt.round(3) %><%
34
+ end
35
+ end
36
+ results['min'][dc_coords['Datacenter']] = min
37
+ results['max'][dc_coords['Datacenter']] = max
38
+ results['p50'][dc_coords['Datacenter']] = percentile(rtts, 0.5)
39
+ results['p90'][dc_coords['Datacenter']] = percentile(rtts, 0.9)
40
+ end
41
+
42
+ # Now, we iterate over aggregated results, type is min, max...
43
+ results.each do |type, res_type|
44
+ %>
45
+
46
+ # HELP consul_wan_dc_<%= type %>_seconds <%= type %> round trip with other DCs
47
+ # TYPE consul_wan_dc_<%= type %>_seconds gauge
48
+ <%
49
+ # Iterate over destinations
50
+ res_type.each do |dst, val|
51
+ %>consul_wan_dc_<%= type %>_seconds{from="<%= my_dcname %>",to="<%= dst %>"} <%= val.round(3) %>
52
+ <%
53
+ end
54
+ end
55
+ end
56
+ %>
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.26.1
4
+ version: 1.27.2
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-03-27 00:00:00.000000000 Z
11
+ date: 2020-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: em-http-request
@@ -198,6 +198,7 @@ files:
198
198
  - samples/all_services.txt.erb
199
199
  - samples/all_services_multi_agents.txt.erb
200
200
  - samples/all_templates.erb
201
+ - samples/checks_in_warning_or_critical_state.yaml.erb
201
202
  - samples/consul-ui/README.md
202
203
  - samples/consul-ui/common/footer.html.erb
203
204
  - samples/consul-ui/common/header.html.erb
@@ -229,7 +230,9 @@ files:
229
230
  - samples/criteo/haproxy.cfg.erb
230
231
  - samples/debug/compare_connect_services.txt.erb
231
232
  - samples/demos/compute_pricing.txt.erb
233
+ - samples/display_timestamped_changes.txt.erb
232
234
  - samples/find_all_invalid_dns_labels.json.erb
235
+ - samples/find_nodes_in_catalog_but_not_in_members.json.erb
233
236
  - samples/ha_proxy.cfg.erb
234
237
  - samples/haproxy_dns.cfg.erb
235
238
  - samples/hosts.erb
@@ -239,6 +242,7 @@ files:
239
242
  - samples/members.json.erb
240
243
  - samples/metrics.erb
241
244
  - samples/prometheus_consul_coordinates.erb
245
+ - samples/prometheus_datacenter_coordinates.erb
242
246
  - samples/render_template_from_kv.erb
243
247
  - samples/sample_keys.html.erb
244
248
  - samples/service_checks_metrics.erb
@@ -269,8 +273,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
269
273
  - !ruby/object:Gem::Version
270
274
  version: '0'
271
275
  requirements: []
272
- rubyforge_project:
273
- rubygems_version: 2.7.7
276
+ rubygems_version: 3.0.8
274
277
  signing_key:
275
278
  specification_version: 4
276
279
  summary: Implementation of Consul template using Ruby and .erb templating language