consul-templaterb 1.0.7 → 1.0.8

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: dff2b22aa59dd10c76e17b02ad83bc2d3f5d0b6e2e2502ad94c2436c9c93f074
4
- data.tar.gz: f41245cc9d75fd2c326fa573643e6dee67eb2e9743c9862da2710e41114fba3f
3
+ metadata.gz: 49568fc4a6f043e390484820ea383914a31d35c99149669a4b77772bfeed0504
4
+ data.tar.gz: f56e6cd3882cb1154219c3c56da2ae9b484e9c6196c685980c296e713c6f9650
5
5
  SHA512:
6
- metadata.gz: 6a2cc201720dd43f08ac085e7df34bafaa63379a3eabd801e188f587a2ff5448ec2c657e7984c47dddc516a0fc6d8ce61c5ab4cb426ff76101e671f0c523855e
7
- data.tar.gz: 7dea5e8d25aade2cfb621faa461f496183dcdfa77a8b3862fd2e030d042c05722692517bdaf8de382566946948eaa6aaeb3b91596c7bda08c1ef17690cc1f8dc
6
+ metadata.gz: 7e241be76b837635ef83dff4ec76191bba016cdb7e040b4c17325458465d6b10ae5a8430527484be30a2b0bf05d0a86350e970742b570766d14578a621f817fb
7
+ data.tar.gz: 9f4ccd3d0222a28abf6bcb89b46e101c627d736c73b6451a39f9bb4507e68713b1a88abfaf66d4e5a152de09d84b55ac486af2f2a692967a26762bb62129fd84
data/.rubocop.yml CHANGED
@@ -13,7 +13,7 @@ Metrics/BlockNesting:
13
13
  Max: 4
14
14
 
15
15
  Metrics/ClassLength:
16
- Max: 160
16
+ Max: 180
17
17
 
18
18
  Metrics/CyclomaticComplexity:
19
19
  Max: 15
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  ## (UNRELEASED)
2
2
 
3
+ ## 1.0.8 (March 18, 2018)
4
+
5
+ IMPROVEMENTS:
6
+
7
+ * More clever behaviour regarding processes on First completed rendering phase
8
+ * All samples/*.html.erb templates are w3c compatibles without errors
9
+ * Look and features improvements for samples/*.html.erb
10
+ * Added optional parameters to sub-template in `render_file`
11
+ * Added `param(name, default_value)` to retrieve parameter from sub-template
12
+ * Use `CONSUL_HTTP_TOKEN` if present in environment variables to get the token
13
+ * Added [TemplateAPI.md](TemplateAPI.md] for documenting functions
14
+
3
15
  ## 1.0.7 (March 16, 2018)
4
16
 
5
17
  BUG FIXES:
@@ -14,7 +26,7 @@ IMPROVEMENTS:
14
26
 
15
27
  IMPROVEMENTS:
16
28
 
17
- * Adds http:// to Consul URL if missing since `$CONSUL_HTTP_ADDR` environment
29
+ * Adds `http://` to Consul URL if missing since `$CONSUL_HTTP_ADDR` environment
18
30
  variable might not have it
19
31
  * Updated gem description with more accurate information
20
32
  * samples: removed KV/nodes from services.html.erb
data/Gemfile CHANGED
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in consul-templaterb.gemspec
4
4
  gemspec
5
- gem 'bootstrap', '~> 4.0.0.alpha6'
5
+ gem 'bootstrap', '~> 4.0.0'
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
- # Consul::Templaterb [![Build Status](https://api.travis-ci.org/criteo/consul-templaterb.svg?branch=master)](https://travis-ci.org/criteo/consul-templaterb)
1
+ # consul-templaterb [![Build Status](https://api.travis-ci.org/criteo/consul-templaterb.svg?branch=master)](https://travis-ci.org/criteo/consul-templaterb)
2
2
 
3
- This GEM is both a library and an executable that allows to generate files
3
+ The ruby GEM [consul-templaterb](https://rubygems.org/gems/consul-templaterb)
4
+ is both a library and an executable that allows to generate files
4
5
  using data from Consul (Discovery and Key/Value Store) easily using ruby's
5
6
  erb templates. It also support launching programs and baby-sitting processes
6
7
  when rendering the files, thus notifying programs when data do change.
@@ -9,8 +10,9 @@ It is intended for user accustomed to expressiveness or Ruby templating (ERB),
9
10
  allowing for more flexibility and features than Go templates.
10
11
 
11
12
  It also allows to use all of ruby language, especially useful for generating
12
- files in several formats (JSON, XML) where text substitutions are hard to get
13
- right.
13
+ files in several formats ([JSON](samples/consul_template.json.erb),
14
+ [XML](samples/consul_template.xml.erb)) for which text substitutions are hard
15
+ to get right (escaping, attributes encoding...).
14
16
 
15
17
  It also focuses on good performance and lightweight usage of bandwidth,
16
18
  especially for very large clusters and watching lots of services.
@@ -19,6 +21,9 @@ For complicated rendering of templates and large Consul Clusters, it usually
19
21
  renders faster with a more predictable way the template than the original
20
22
  consul-template.
21
23
 
24
+ It provides a very [simple API](TemplateAPI.md) to write your own templates
25
+ with fully [working examples](samples/).
26
+
22
27
  ## Differences with HashiCorp's consul-template
23
28
 
24
29
  [Hashicorp's Consul Template](https://github.com/hashicorp/consul-template)
@@ -45,13 +50,16 @@ Compared to consul-template, consul-templaterb offers the following features:
45
50
 
46
51
  * Hot-Reload of template files
47
52
  * Bandwidth limitation per endpoint (will soon support dynamic bandwidth limiter)
48
- * Supports baby sitting of multiple processes
53
+ * Supports launch and supervision of multiple child processes
54
+ * Supports launching commands when files do change on disk (reload commands...)
49
55
  * Supports all Ruby features (ex: base64, real JSON/XML generation...)
50
56
  * Information about bandwidth
51
57
 
52
- The executable supports close semantics to Consul template, it also supports
53
- commands when files are modified and supervision of multiple processes with
54
- ability to send signals to those processes when the files do change.
58
+ The executable supports semantics and command line flags and options similar to
59
+ HashiCorp's Consul-template, so many flags you might use in consul-template will
60
+ work in a similar way. It also supports the same environment variable
61
+ `CONSUL_HTTP_ADDR` to find the Consul Agent to query and 'CONSUL_HTTP_TOKEN' to
62
+ get the token.
55
63
 
56
64
  ## Installation
57
65
 
@@ -96,6 +104,7 @@ installed:
96
104
 
97
105
  ```shell
98
106
  $ gem contents consul-templaterb|grep samples
107
+ [...]
99
108
  ```
100
109
 
101
110
  Will output the path where the samples are being installed, you can copy the directory
@@ -103,11 +112,16 @@ somewhere and then issue the command:
103
112
 
104
113
  ```shell
105
114
  $ consul-templaterb samples/*.html.erb
115
+ Using samples/checks.html output for samples/checks.html.erb
116
+ [...]
106
117
  ```
107
118
 
108
119
  It will render a full web site you may browse to look in real time the status of your
109
120
  Consul Cluster.
110
121
 
122
+ You can now have a look to the [API Documentation](TemplateAPI.md) to modify existing
123
+ templates or write your owns, it is very easy!
124
+
111
125
  ## Usage of consul-templaterb
112
126
 
113
127
  ### Show help
@@ -168,8 +182,8 @@ meaning that if 2 results of templates are modified at the same time, the signal
168
182
  sent only once (it is helpful for instance if your app is using several configurations
169
183
  files that must be consistent all together).
170
184
 
171
- Signals can be customized per process. Two signals are supported with options --sig-reload and
172
- --sig-term. When the option is added, the next --exec options to start a process will use the
185
+ Signals can be customized per process. Two signals are supported with options `--sig-reload` and
186
+ `--sig-term`. When the option is added, the next `--exec` options to start a process will use the
173
187
  given signal. By default, HUP will be sent to reload events (you can use NONE to avoid sending any
174
188
  reload signal), TERM will be used when leaving consul-templaterb.
175
189
 
@@ -189,90 +203,36 @@ by consul-templaterb.
189
203
 
190
204
  ### Samples
191
205
 
192
- Have a look into the [samples/](samples/) directory to browse example files.
206
+ Have a look into the [samples/](samples/) directory to browse example files which contains those
207
+ examples:
208
+
209
+ 1. [List all nodes on Cluster](samples/nodes.html.erb)
210
+ 2. [Show all services in Cluster](samples/services.html.erb)
211
+ 3. [Show all Service Checks and their output](samples/checks.html.erb)
212
+ 4. [Show all Key/Values nicely](samples/keys.html.erb)
213
+ 5. [Show Choregraphies - work on content of K/V with JSON](samples/criteo_choregraphies.html.erb)
214
+ 6. [Services in XML](samples/consul_template.xml.erb)
215
+ 7. [Services in JSON](samples/consul_template.json.erb)
216
+ 8. [Generate HaProxy Configuration](samples/ha_proxy.cfg.erb)
193
217
 
194
218
  If you want to test it quickly, you might try with (assuming you consul agent is listening on
195
- http://localhost:8500):
219
+ `http://localhost:8500`):
196
220
 
197
- ```
221
+ ```shell
198
222
  $ be bin/consul-templaterb -c 'http://localhost:8500' samples/*.html.erb
223
+ [...]
199
224
  ```
200
225
 
201
226
  It will generate a full website in samples/ directory with lots of Consul information ready to
202
227
  use (website updated automagically when values to change).
203
228
 
204
- ## Template development
205
-
206
- Here are the various functions you might use in your templates.
207
-
208
- For each function, mandatory arguments are specified at the beginning while optional ones are marked with `[]`.
209
- Most of them support the optional dc attribute to access data from another datacenter. If the `dc`
210
- attribute is not specified, the function will output data from the current datacenter.
211
-
212
- To ease template development, `consul-templaterb` supports HOT reload of templates, thus it is possible to
213
- develop the templates interactively. While developing, it is possible to use the switch `--hot-reload=keep`,
214
- thus the application will display a warning if the template is invalid and won't stop
215
- (`--hot-reload=die` is the default, thus if the hot-reloaded template has issue, the application will die).
216
-
217
- ### datacenters()
218
-
219
- [Get the list of datacenters as string array](https://www.consul.io/api/catalog.html#list-datacenters).
220
-
221
- ### services([dc: datacenter], [tag: tagToFilterWith])
222
-
223
- [List the services matching the optional tag filter](https://www.consul.io/api/catalog.html#list-services),
224
- if tag is not specified, will match all the services. Note that this endpoint performs client side tag
225
- filtering for services to ease templates development since this feature is not available on Consul's endpoint.
226
-
227
- ### service(serviceName, [dc: datacenter], [tag: tagToFilterWith], [passing: true])
228
-
229
- [List the instances](https://www.consul.io/api/health.html#list-nodes-for-service) of a service having the given
230
- optional tag. If no tag is specified, will return all instances of service. By default, it will return all the
231
- well services that are passing or not. An optional argument passing might be used to retrieve only passing instances.
229
+ All templates are validated using the Travis CI, so all should be working for your Consul
230
+ Configuration.
232
231
 
233
- ### nodes([dc: datacenter])
234
-
235
- [List all the nodes of selected datacenter](https://www.consul.io/api/catalog.html#list-nodes). No filtering is
236
- applied.
237
-
238
- ### node(nodeNameOrId, [dc: datacenter])
239
-
240
- [List all the services of a given Node](https://www.consul.io/api/catalog.html#list-services-for-node) using its
241
- name or its ID. If DC is specified, will lookup for given node in another datacenter.
242
-
243
- ### checks_for_service(name, dc: nil, passing: false, tag: nil)
244
-
245
- [Find all the checks](https://www.consul.io/api/health.html#list-checks-for-service) of a given service.
246
-
247
- ### kv(name = nil, dc: nil, keys: nil, recurse: false)
248
-
249
- [Read keys from KV Store](https://www.consul.io/api/kv.html#read-key). It can be used for both listing the keys and
250
- getting the values. See the file in samples [keys.html.erb](samples/keys.html.erb) for a working example.
251
-
252
- ### agent_metrics()
253
-
254
- [Get the metrics of Consul Agent](https://www.consul.io/api/agent.html#view-metrics). Since this endpoint does
255
- not support blocking queries, data will be refreshed every few seconds, but will not use blocking queries
256
- mechanism.
257
-
258
- ### agent_self()
259
-
260
- [Get the configuration of Consul Agent](https://www.consul.io/api/agent.html#read-configuration).
261
- Since this endpoint does not support blocking queries, data will be refreshed every few seconds,
262
- but will not use blocking queries mechanism.
263
-
264
- ### render_file RELATIVE_PATH_TO_ERB_FILE
265
-
266
- This allow to include a template file into another one. Beware, it does not check for infinite recursion!
267
- The template can be either a static file either another template. The file has to be a valid template, but
268
- can also be raw text (if it is a valid template) and is resolved with a relative path regarding the file
269
- including it.
270
-
271
- Example:
232
+ ## Template development
272
233
 
273
- ```erb
274
- <%= render_file 'header.html.erb' %>
275
- ```
234
+ Please look at [the template API](TemplateAPI.md) to have a list of all functions you might use for your
235
+ templates. Also have a look to [samples/](samples/) directory to have full working examples.
276
236
 
277
237
  ## Development
278
238
 
@@ -292,6 +252,8 @@ Here are the known bugs of the application:
292
252
  on very large clusters or when watching thousands of individual KV keys.
293
253
  * [ ] render_file might create an infinite recursion if a template includes itself indirectly.
294
254
 
255
+ Please consult [CHANGELOG.md](CHANGELOG.md) for fixed bugs.
256
+
295
257
  ## TODO
296
258
 
297
259
  * [ ] Hashi's Vault support
data/TemplateAPI.md ADDED
@@ -0,0 +1,84 @@
1
+ # Template API for writting templates
2
+
3
+ Here are the various functions you might use in your templates.
4
+
5
+ For each function, mandatory arguments are specified at the beginning while optional ones are marked with `[]`.
6
+ Most of them support the optional dc attribute to access data from another datacenter. If the `dc`
7
+ attribute is not specified, the function will output data from the current datacenter.
8
+
9
+ To ease template development, `consul-templaterb` supports HOT reload of templates, thus it is possible to
10
+ develop the templates interactively. While developing, it is possible to use the switch `--hot-reload=keep`,
11
+ thus the application will display a warning if the template is invalid and won't stop
12
+ (`--hot-reload=die` is the default, thus if the hot-reloaded template has issue, the application will die).
13
+
14
+ Have a look to [samples/](samples/) directory to start writing your own templates.
15
+
16
+ ## datacenters()
17
+
18
+ [Get the list of datacenters as string array](https://www.consul.io/api/catalog.html#list-datacenters).
19
+
20
+ ## services([dc: datacenter], [tag: tagToFilterWith])
21
+
22
+ [List the services matching the optional tag filter](https://www.consul.io/api/catalog.html#list-services),
23
+ if tag is not specified, will match all the services. Note that this endpoint performs client side tag
24
+ filtering for services to ease templates development since this feature is not available on Consul's endpoint.
25
+
26
+ ## service(serviceName, [dc: datacenter], [tag: tagToFilterWith], [passing: true])
27
+
28
+ [List the instances](https://www.consul.io/api/health.html#list-nodes-for-service) of a service having the given
29
+ optional tag. If no tag is specified, will return all instances of service. By default, it will return all the
30
+ well services that are passing or not. An optional argument passing might be used to retrieve only passing instances.
31
+
32
+ ## nodes([dc: datacenter])
33
+
34
+ [List all the nodes of selected datacenter](https://www.consul.io/api/catalog.html#list-nodes). No filtering is
35
+ applied.
36
+
37
+ ## node(nodeNameOrId, [dc: datacenter])
38
+
39
+ [List all the services of a given Node](https://www.consul.io/api/catalog.html#list-services-for-node) using its
40
+ name or its ID. If DC is specified, will lookup for given node in another datacenter.
41
+
42
+ ## checks_for_service(name, dc: nil, passing: false, tag: nil)
43
+
44
+ [Find all the checks](https://www.consul.io/api/health.html#list-checks-for-service) of a given service.
45
+
46
+ ## kv(name = nil, dc: nil, keys: nil, recurse: false)
47
+
48
+ [Read keys from KV Store](https://www.consul.io/api/kv.html#read-key). It can be used for both listing the keys and
49
+ getting the values. See the file in samples [keys.html.erb](samples/keys.html.erb) for a working example.
50
+
51
+ ## agent_metrics()
52
+
53
+ [Get the metrics of Consul Agent](https://www.consul.io/api/agent.html#view-metrics). Since this endpoint does
54
+ not support blocking queries, data will be refreshed every few seconds, but will not use blocking queries
55
+ mechanism.
56
+
57
+ ## agent_self()
58
+
59
+ [Get the configuration of Consul Agent](https://www.consul.io/api/agent.html#read-configuration).
60
+ Since this endpoint does not support blocking queries, data will be refreshed every few seconds,
61
+ but will not use blocking queries mechanism.
62
+
63
+ ## render_file(relative_path_to_erb_file, [params={}])
64
+
65
+ This allow to include a template file into another one. Beware, it does not check for infinite recursion!
66
+ The template can be either a static file either another template. The file has to be a valid template, but
67
+ can also be raw text (if it is a valid template) and is resolved with a relative path regarding the file
68
+ including it.
69
+
70
+ Example:
71
+
72
+ ```erb
73
+ <%= render_file('common/header.html.erb', title: 'My Title') %>
74
+ ```
75
+
76
+ Will render header.html.erb with parameter title = 'My Title'. `title` can then be accessed within
77
+ the template using `param('title', 'My default Value')` in the `header.html.erb` file.
78
+
79
+ ## param(parameter_name, [default_value: nil])
80
+
81
+ Can be used within a template to access a parameter. Parameters can be specified with `render_file`
82
+ directive. Optional value `default_value` allow to get a value if parameter has not been set.
83
+
84
+ See [samples/common/header.html.erb](samples/common/header.html.erb) for example of usage.
@@ -22,7 +22,7 @@ options = {
22
22
  network: false
23
23
  },
24
24
  base_url: ENV['CONSUL_HTTP_ADDR'] || 'http://localhost:8500',
25
- token: nil,
25
+ token: ENV['CONSUL_HTTP_TOKEN'] || nil,
26
26
  retry_duration: 10, # On error, retry after n seconds
27
27
  min_duration: 5, # On sucess and when differences are found
28
28
  retry_on_non_diff: 3, # On success but when there are not differences
@@ -113,16 +113,14 @@ optparse = OptionParser.new do |opts|
113
113
  sig_term = cur_sig_term
114
114
  consul_engine.add_template_callback do |all_ready, template_manager, results|
115
115
  if all_ready
116
- modified = results.reduce(false) { |a, e| a || (e.ready? && e.modified) }
117
- if modified
118
- if @programs[cmd].nil?
119
- STDERR.puts "[EXEC] Starting process: #{cmd}... on_reload=#{sig_reload ? sig_reload : 'NONE'} on_term=#{sig_term}"
120
- @programs[cmd] = Consul::Async::ProcessHandler.new(cmd, sig_reload: sig_reload, sig_term: sig_term)
121
- @programs[cmd].start
122
- else
123
- @programs[cmd].reload
124
- end
125
- elsif !@programs[cmd].nil?
116
+ modified = results.reduce(false) { |a, e| a || e.modified }
117
+ if @programs[cmd].nil?
118
+ STDERR.puts "[EXEC] Starting process: #{cmd}... on_reload=#{sig_reload ? sig_reload : 'NONE'} on_term=#{sig_term}"
119
+ @programs[cmd] = Consul::Async::ProcessHandler.new(cmd, sig_reload: sig_reload, sig_term: sig_term)
120
+ @programs[cmd].start
121
+ else
122
+ # At least one template has been modified
123
+ @programs[cmd].reload if modified
126
124
  begin
127
125
  @programs[cmd].process_status
128
126
  rescue Consul::Async::ProcessDoesNotExist => e
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
26
26
  end
27
27
  spec.bindir = 'bin'
28
28
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
29
+ spec.extra_rdoc_files = ['README.md', 'TemplateAPI.md']
29
30
  spec.require_paths = ['lib']
30
31
 
31
32
  spec.require_paths = ['lib']
@@ -24,6 +24,10 @@ module Consul
24
24
  errors: 0,
25
25
  bytes_read: 0
26
26
  }
27
+ @context = {
28
+ current_erb_path: nil,
29
+ params: {}
30
+ }
27
31
  end
28
32
 
29
33
  # https://www.consul.io/api/health.html#list-nodes-for-service
@@ -79,6 +83,14 @@ module Consul
79
83
  create_if_missing(path, query_params) { ConsulAgentMetrics.new(ConsulEndpoint.new(conf, path, true, query_params, default_value)) }
80
84
  end
81
85
 
86
+ # Return a param of template
87
+ def param(key, default_value = nil)
88
+ v = @context[:params][key]
89
+ v = @context[:params][key.to_sym] unless v
90
+ v = default_value unless v
91
+ v
92
+ end
93
+
82
94
  # https://www.consul.io/api/catalog.html#list-services
83
95
  def services(dc: nil, tag: nil)
84
96
  path = '/v1/catalog/services'
@@ -107,18 +119,22 @@ module Consul
107
119
  create_if_missing(path, query_params) { ConsulTemplateKV.new(ConsulEndpoint.new(conf, path, true, query_params, default_value), name) }
108
120
  end
109
121
 
110
- def render_file(path)
111
- new_path = File.expand_path(path, File.dirname(@current_erb_path))
122
+ # render a relative file with the given params accessible from template
123
+ def render_file(path, params = {})
124
+ new_path = File.expand_path(path, File.dirname(@context[:current_erb_path]))
112
125
  raise "render_file ERROR: #{path} is resolved as #{new_path}, but the file does not exists" unless File.exist? new_path
113
- render(File.read(new_path), new_path)
126
+ render(File.read(new_path), new_path, params)
114
127
  end
115
128
 
116
- def render(tpl, tpl_file_path)
129
+ def render(tpl, tpl_file_path, params = {})
117
130
  # Ugly, but allow to use render_file well to support stack of calls
118
- old_value = @current_erb_path
119
- @current_erb_path = tpl_file_path
131
+ old_value = @context
132
+ @context = {
133
+ current_erb_path: tpl_file_path,
134
+ params: params
135
+ }
120
136
  result = ERB.new(tpl).result(binding)
121
- @current_erb_path = old_value
137
+ @context = old_value
122
138
  result
123
139
  rescue StandardError => e
124
140
  e2 = InvalidTemplateException.new e
@@ -141,7 +157,7 @@ module Consul
141
157
  end
142
158
  if not_ready.count.positive?
143
159
  STDERR.print "[INFO] Waiting for data from #{not_ready.count}/#{not_ready.count + ready} endpoints: #{not_ready[0..2]}..."
144
- return [false, false, '']
160
+ return [false, false, nil]
145
161
  end
146
162
  if to_cleanup.count > 1
147
163
  STDERR.puts "[INFO] Cleaned up #{to_cleanup.count} endpoints: #{to_cleanup}"
@@ -14,6 +14,7 @@ module Consul
14
14
  @templates = []
15
15
  @template_callbacks = []
16
16
  @hot_reload_failure = 'die'
17
+ @all_templates_rendered = false
17
18
  end
18
19
 
19
20
  def add_template_callback(&block)
@@ -36,12 +37,16 @@ module Consul
36
37
  begin
37
38
  results = template_renders.map(&:run)
38
39
  all_ready = results.reduce(true) { |a, e| a && e.ready? }
40
+ if !@all_templates_rendered && all_ready
41
+ @all_templates_rendered = true
42
+ STDERR.puts "[INFO] First rendering of #{results.count} templates completed"
43
+ end
39
44
  begin
40
45
  @template_callbacks.each do |c|
41
46
  c.call([all_ready, template_manager, results])
42
47
  end
43
48
  rescue StandardError => cbk_error
44
- STDERR.puts "Error in callback: #{cbk_error.inspect}"
49
+ STDERR.puts "[ERROR] callback error: #{cbk_error.inspect}"
45
50
  raise cbk_error
46
51
  end
47
52
  rescue StandardError => e
@@ -58,7 +58,8 @@ module Consul
58
58
  end
59
59
 
60
60
  def write
61
- success, modified, @last_result = @template_manager.write(@output_file, @template, @last_result, template_file)
61
+ success, modified, last_res = @template_manager.write(@output_file, @template, @last_result, template_file)
62
+ @last_result = last_res if last_res
62
63
  [success, modified, @last_result]
63
64
  end
64
65
 
@@ -1,5 +1,5 @@
1
1
  module Consul
2
2
  module Async
3
- VERSION = '1.0.7'.freeze
3
+ VERSION = '1.0.8'.freeze
4
4
  end
5
5
  end
@@ -1,3 +1,4 @@
1
+ <%= render_file('common/header.html.erb', title: 'Consul Checks') %>
1
2
  <%
2
3
  # This template can be configure the following way with environment variables
3
4
  # Environment variables to filter services/instances
@@ -14,13 +15,13 @@
14
15
  'info'
15
16
  end
16
17
  end
17
- %><%= render_file 'common/header.html.erb' %>
18
- <style type="text/css">
18
+ %>
19
+ <style>
19
20
  .check {
20
21
  transition: opacity 1s ease-out;
21
22
  }
22
23
  </style>
23
- <script type="text/javascript">
24
+ <script>//<!--
24
25
  function updateStates() {
25
26
  states = ['passing', 'warning', 'critical']
26
27
  stylesheet = document.getElementById('css-states');
@@ -30,15 +31,16 @@
30
31
  is_checked = document.getElementById('show_'+s).checked
31
32
  var to_append = "." + s + " { ";
32
33
  if (is_checked) {
33
- to_append += "opacity: 1; height: auto;"
34
+ to_append += "display: block"
34
35
  } else {
35
- to_append += "opacity: 0; height: 0; overflow: hidden;"
36
+ to_append += "display:none"
36
37
  }
37
38
  to_append += " }\r\n"
38
39
  txt += to_append;
39
40
  }
40
41
  stylesheet.textContent = txt;
41
42
  }
43
+ //->
42
44
  </script>
43
45
  <div class="container-fluid">
44
46
  <div class="row">
@@ -48,33 +50,66 @@
48
50
  all_services = services(tag: services_tag_filter)
49
51
  all_services.each do |service_name, tags|
50
52
  %>
51
- <li><a href="#service_<%= service_name %>"><%= service_name %></a></li>
53
+ <li><a title="<%= tags.sort.join(', ') %>" href="#service_<%= service_name %>"><%= service_name %></a></li>
52
54
  <% end %>
53
55
  </ul>
54
56
  </nav>
55
57
  <main class="col-10">
56
58
  <div class="float-right">
57
- <div class="state-selector">
59
+ <span class="state-selector">
58
60
  <input id="show_passing" type="checkbox" class="checks-visibility" value="passing" checked="checked" onchange="updateStates()"/>
59
61
  <label for="show_passing"><span class="badge badge-success">Passing</span></label>
60
- </div>
61
- <div class="state-selector">
62
+ </span>
63
+ <span class="state-selector">
62
64
  <input id="show_warning" type="checkbox" class="checks-visibility" value="warning" checked="checked" onchange="updateStates()"/>
63
65
  <label for="show_warning"><span class="badge badge-warning">Warning</span></label>
64
- </div>
65
- <div class="state-selector">
66
+ </span>
67
+ <span class="state-selector">
66
68
  <input id="show_critical" type="checkbox" class="checks-visibility" value="critical" checked="checked" onchange="updateStates()"/>
67
69
  <label for="show_critical"><span class="badge badge-danger">Critical</span></label>
68
- </div>
69
- </div>
70
+ </span>
71
+ </div>
70
72
 
71
73
  <h1 id="services">Services <%= services_tag_filter ? " tag: #{services_tag_filter}" : 'No filtering' %></h1>
72
- <% all_services.each do |service_name, tags| %>
73
- <div class="service" id="service_<%= service_name %>">
74
- <h2>Service <%= service_name %></h2>
74
+ <% all_services.each do |service_name, tags|
75
+ passing = 0
76
+ warning = 0
77
+ critical = 0
78
+ service_state = 'passing'
79
+ sum = 0
80
+ checks_for_service(service_name).each do |check|
81
+ st = check['Status']
82
+ sum += 1
83
+ case st
84
+ when 'warning'
85
+ warning += 1
86
+ when 'passing'
87
+ passing += 1
88
+ else
89
+ critical += 1
90
+ end
91
+ end
92
+ service_state = 'warning' if warning > 0
93
+ service_state = 'critical' if critical > 0
94
+ %>
95
+ <div class="service <%= service_state %>" id="service_<%= service_name %>">
96
+ <h2 class="service-name text-<%= status_to_class(service_state) %>">Service <%= service_name %>
97
+ <span class="float-right">
98
+ <span class="badge badge-pill badge-success"><%= passing > 0 ? passing : nil %></span>
99
+ <span class="badge badge-pill badge-warning"><%= warning > 0 ? warning : nil %></span>
100
+ <span class="badge badge-pill badge-danger"><%= critical > 0 ? critical : nil %></span>
101
+ </span>
102
+ </h2>
103
+ <% if sum > 0
104
+ %><div class="progress">
105
+ <div title="<%= "#{passing} / #{sum}" %>" class="progress-bar bg-success" role="progressbar" style="width:<%= (100.0 * passing / sum).round(2) %>%" aria-valuenow="<%= passing %>" aria-valuemin="0" aria-valuemax="<%= sum %>"></div>
106
+ <div title="<%= "#{warning} / #{sum}" %>" class="progress-bar bg-warning" role="progressbar" style="width:<%= (100.0 * warning / sum).round(2) %>%" aria-valuenow="<%= warning %>" aria-valuemin="0" aria-valuemax="<%= sum %>"></div>
107
+ <div title="<%= "#{critical} / #{sum}" %>" class="progress-bar bg-danger" role="progressbar" style="width:<%= (100.0 * critical / sum).round(2) %>%" aria-valuenow="<%= critical %>" aria-valuemin="0" aria-valuemax="<%= sum %>"></div>
108
+ </div>
109
+ <% end %>
75
110
  <div class="list-group">
76
111
  <% checks_for_service(service_name).each do |check| %>
77
- <div class="list-group-item check <%= check['Status'] %>" id="<%= check['CheckID'] %>">
112
+ <div class="list-group-item check <%= check['Status'] %>" id="<%= check['Node'] + check['CheckID'] %>">
78
113
  <div class="d-flex align-items-center justify-content-between">
79
114
  <h5>
80
115
  <%= check['Name'] %> / <%= check['Node'] %>
@@ -91,5 +126,5 @@
91
126
  </main>
92
127
  </div>
93
128
  </div>
94
- <script type="text/javascript">updateStates();</script>
95
- <%= render_file 'common/footer.html.erb' %>
129
+ <script>updateStates();</script>
130
+ <%= render_file('common/footer.html.erb') %>
@@ -1,5 +1,3 @@
1
- </div>
2
- </main>
3
1
  <!-- Bootstrap core JavaScript
4
2
  ================================================== -->
5
3
  <!-- Latest compiled and minified JavaScript -->
@@ -9,35 +9,30 @@
9
9
  %><!DOCTYPE html>
10
10
  <html lang="en">
11
11
  <head>
12
- <meta charset="utf-8">
13
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
14
- <meta name="description" content="Display Consul information">
15
- <meta name="author" content="Criteo">
16
- <meta http-equiv="refresh" content="<%= ENV['REFRESH'] || '600' %>"/>
17
-
18
- <title><%= ENV['TITLE'] || 'Consul Real Time information'%></title>
19
-
12
+ <meta charset="utf-8"/>
13
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
14
+ <meta name="description" content="Display Consul information"/>
15
+ <meta name="author" content="Criteo"/>
16
+ <meta http-equiv="refresh" content="<%= param('refresh', ENV['REFRESH'] || '600') %>"/>
17
+ <title><%= param('title', 'Consul Real Time information') %></title>
20
18
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
21
- <style type="text/css">
19
+ <style>
22
20
  body {
23
- padding-top: 5rem;
21
+ padding-top: 5rem;
24
22
  }
25
23
  .check {
26
24
  transition: opacity 1s ease-out;
27
25
  }
28
26
  </style>
29
- <style type="text/css" id="css-states">
27
+ <style id="css-states">
30
28
  </style>
31
29
  </head>
32
-
33
30
  <body>
34
-
35
31
  <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
36
32
  <a class="navbar-brand" href="#">Consul</a>
37
33
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
38
34
  <span class="navbar-toggler-icon"></span>
39
35
  </button>
40
-
41
36
  <div class="collapse navbar-collapse" id="navbarsExampleDefault">
42
37
  <ul class="navbar-nav mr-auto">
43
38
  <% tools.each do |tool| %>
@@ -1,10 +1,10 @@
1
1
  <!doctype html>
2
- <html lang="fr">
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8"/>
5
5
  <meta http-equiv="refresh" content="300"/>
6
6
  <title>Consul Full Info</title>
7
- <style type="text/css">
7
+ <style>
8
8
  .passing {
9
9
  color:green;
10
10
  }
@@ -50,7 +50,7 @@
50
50
 
51
51
  <h1 id="services">List all services instances sorted by node name</h1>
52
52
  <% services.each do |service_name, tags|
53
- %><h2 id="service_<%= service_name %>" title="<%= tags %>"><%= service_name %> <a class="qlink" href="#service_<%= service_name %>">&#128279;</a></h2>
53
+ %><h2 id="service_<%= service_name %>" title="<%= tags.join(', ') %>"><%= service_name %> <a class="qlink" href="#service_<%= service_name %>">&#128279;</a></h2>
54
54
  <ul>
55
55
  <% service(service_name).sort {|a,b| a['Node']['Node'] <=> b['Node']['Node'] }.each do |snode|
56
56
  tags = snode['Service']['Tags'].sort
@@ -68,7 +68,7 @@
68
68
  end
69
69
  %><li><a <%= url ? "href=\"#{url}\"" : nil %>><%=
70
70
  snode['Node']['Node'] %><%= port %></a>
71
- <span class="tags"><%= snode['Service']['Tags'].sort %></tag>
71
+ <span class="tags"><%= snode['Service']['Tags'].sort %></span>
72
72
  <span class="statuses"><%
73
73
  snode['Checks'].each do |c| %> <span title="<%= c['Name']%>" class="<%= c['Status'] %>"><%= c['Status']
74
74
  %></span><% end if snode['Checks'] %></span></li>
@@ -80,14 +80,14 @@
80
80
  <h1 id="nodes">List all nodes for DC, sorted by name</h1>
81
81
  <ul>
82
82
  <% nodes.sort {|a,b| a['Node'] <=> b['Node'] }.each do |snode|
83
- %> <li id="node_#{snode['ID']"><%= snode['Address'].ljust(16) %> <%= snode['Node'] %></li>
83
+ %> <li id="node_<%= snode['ID'] %>"><%= snode['Address'].ljust(16) %> <%= snode['Node'] %></li>
84
84
  <% end %>
85
85
  </ul>
86
86
 
87
87
  <h1 id="kv">KV of Current DC</h1>
88
88
  <ul>
89
89
  <% kv(keys:true).each do |key|
90
- %><li id="kv_#{key}"><%= key %></li>
90
+ %><li id="kv_<%= key %>"><%= key %></li>
91
91
  <% end %>
92
92
  </ul>
93
93
  </body>
@@ -2,8 +2,8 @@
2
2
  require 'json'
3
3
  require 'date'
4
4
  @current_time = Time.now.utc
5
- %><%= render_file 'common/header.html.erb' %>
6
- <main role="main" class="container">
5
+ %><%= render_file('common/header.html.erb', title: 'Choregraphies', refresh: 30) %>
6
+ <main class="container">
7
7
  <div>
8
8
  <h1>Show all choregraphie information</h1>
9
9
  <div>This page only show choregraphie when at least one holder exists.</div>
@@ -87,4 +87,6 @@
87
87
  <% end %>
88
88
  <% end %>
89
89
  </div>
90
+ </div>
91
+ </main>
90
92
  <%= render_file 'common/footer.html.erb' %>
@@ -1,12 +1,12 @@
1
- <%= render_file 'common/header.html.erb' %>
2
- <% path = ENV['kv_path'] || 'services-data' %>
1
+ <%= render_file('common/header.html.erb', title: 'Keys') %>
2
+ <% path = ENV['kv_path'] || '' %>
3
3
  <% require 'base64'
4
4
  require 'json'
5
5
  require 'date'
6
6
  %>
7
- <main role="main" class="container">
7
+ <main class="container">
8
8
  <div>
9
- <h1>Show all keys under hierarchy <%= path %></h1>
9
+ <h1>Show all keys <%= path == '' ? '' : "under hierarchy #{path}" %></h1>
10
10
  <div id="accordion">
11
11
  <% kv(path, recurse:true).each do |tuple|
12
12
  key = tuple['Key']
@@ -28,11 +28,13 @@
28
28
  </h5>
29
29
  <div id="collapse-<%= key %>" class="collapse" aria-labelledby="heading-<%= key %>" data-parent="#accordion">
30
30
  <div class="card-body">
31
- <pre class="pre-scrollable"><%= val %></pre>
31
+ <pre class="pre-scrollable"><%= ERB::Util.html_escape(val) %></pre>
32
32
  </div>
33
33
  </div>
34
34
  </div>
35
35
  </div>
36
36
  <% end %>
37
37
  </div>
38
+ </div>
39
+ </main>
38
40
  <%= render_file 'common/footer.html.erb' %>
@@ -1,17 +1,14 @@
1
- <%= render_file 'common/header.html.erb' %>
1
+ <%= render_file('common/header.html.erb', title: 'Nodes') %>
2
2
  <h1 id="nodes">List all nodes for DC, sorted by name</h1>
3
3
  <ul>
4
4
  <% nodes.sort {|a,b| a['Node'] <=> b['Node'] }.each do |snode|
5
- %> <li id="node_#{snode['ID']"><a href="ssh://<%= snode['Address']%>"><%= snode['Address'].ljust(16) %></a> <%= snode['Node'] %><%
5
+ %><li id="node_<%= snode['ID'] %>"><a href="ssh://<%= snode['Address']%>"><%= snode['Address'] %></a> <%= snode['Node'] %><%
6
6
  snode['Meta'].each do |k,v|
7
7
  if v && !v.empty?
8
- %>
9
- <span class="badge badge-pill badge-primary float-right"><%= k %>:&nbsp;<%= v%></span>
10
- <%
8
+ %><span class="badge badge-pill badge-primary float-right"><%= k %>:&nbsp;<%= v%></span><%
11
9
  end
12
10
  end if snode['Meta']
13
- %>
14
- </li>
11
+ %></li>
15
12
  <% end %>
16
13
  </ul>
17
14
  <%= render_file 'common/footer.html.erb' %>
@@ -2,6 +2,10 @@
2
2
  # This template can be configure the following way with environment variables
3
3
  # Environment variables to filter services/instances
4
4
  # SERVICES_TAG_FILTER: basic tag filter for service (default HTTP)
5
+ require 'base64'
6
+ require 'json'
7
+ require 'date'
8
+
5
9
  services_tag_filter = ENV['SERVICES_TAG_FILTER']
6
10
  def status_to_class(status)
7
11
  if status == 'passing'
@@ -14,11 +18,41 @@
14
18
  'info'
15
19
  end
16
20
  end
17
- %><%= render_file 'common/header.html.erb' %>
18
- <% require 'base64'
19
- require 'json'
20
- require 'date'
21
- %>
21
+
22
+ def compute_node_status(snode)
23
+ state = 'passing'
24
+ return state unless snode['Checks']
25
+ snode['Checks'].each do |c|
26
+ case c['Status']
27
+ when 'critical'
28
+ return 'critical'
29
+ when 'warning'
30
+ state = 'warning'
31
+ end
32
+ end
33
+ state
34
+ end
35
+
36
+ def compute_service_status(snodes)
37
+ passing = 0
38
+ warning = 0
39
+ critical = 0
40
+ sum = 0
41
+ snodes.each do |snode|
42
+ st = compute_node_status(snode)
43
+ sum += 1
44
+ case st
45
+ when 'passing'
46
+ passing += 1
47
+ when 'warning'
48
+ warning += 1
49
+ else
50
+ critical += 1
51
+ end
52
+ end
53
+ [passing, warning, critical, sum]
54
+ end
55
+ %><%= render_file('common/header.html.erb', title: 'Services') %>
22
56
  <nav>
23
57
  <ul>
24
58
  <li><a href="#datacenters">DataCenters</a></li>
@@ -26,46 +60,75 @@
26
60
  <li><a href="#services">Services with instances</a></li>
27
61
  </ul>
28
62
  </nav>
29
- <h1 id="datacenters">List of all datacenters</h1>
30
- <ul>
31
- <% datacenters.each do |dc| %>
32
- <li id="dc_<%=dc %>%"><%= dc %> with <%= services(dc:dc).keys.count %> services, <%= nodes(dc:dc).count %> nodes</li>
33
- <% end %>
34
- </ul>
35
-
36
- <h1 id="list_services">List of all services in current DC</h1>
37
- <ul>
38
- <% services.each do |service_name, tags|
39
- %>
40
- <li><a href="#service_<%= service_name %>"><%= service_name %></a> <%= tags.sort %></li>
41
- <% end %>
42
- </ul>
43
63
 
44
- <h1 id="services">List all services instances sorted by node name</h1>
45
- <% services.each do |service_name, tags|
46
- %><h2 id="service_<%= service_name %>" title="<%= tags %>"><%= service_name %> <a class="qlink" href="#service_<%= service_name %>">&#128279;</a></h2>
47
- <ul>
48
- <% service(service_name).sort {|a,b| a['Node']['Node'] <=> b['Node']['Node'] }.each do |snode|
49
- tags = snode['Service']['Tags'].sort
50
- addr = snode['Node']['Address']
51
- port_num = snode['Service']['Port'].to_i
52
- port = port_num && port_num > 0 ? ":#{port_num}" : ''
53
- url = if tags.include? 'https'
54
- "https://#{addr}#{port}"
55
- elsif tags.include? 'http'
56
- "http://#{addr}#{port}"
57
- elsif tags.include? 'ftp'
58
- "ftp://#{addr}#{port}"
59
- else
60
- nil
61
- end
62
- %><li><a <%= url ? "href=\"#{url}\"" : nil %>><%=
63
- snode['Node']['Node'] %><%= port %></a>
64
- <span class="tags"><%= snode['Service']['Tags'].sort %></tag>
65
- <span class="statuses"><%
66
- snode['Checks'].each do |c| %> <span title="<%= c['Name']%>" class="<%= c['Status'] %>"><%= c['Status']
67
- %></span><% end if snode['Checks'] %></span></li>
68
- <% end%>
69
- </ul>
70
- <% end%>
64
+ <div class="container-fluid">
65
+ <div class="row">
66
+ <nav id="list_services" class="col-2">
67
+ <ul>
68
+ <%
69
+ all_services = services(tag: services_tag_filter)
70
+ all_services.each do |service_name, tags|
71
+ %>
72
+ <li><a title="<%= tags.sort.join(', ') %>" href="#service_<%= service_name %>"><%= service_name %></a></li>
73
+ <% end %>
74
+ </ul>
75
+ </nav>
76
+ <main class="col-10">
77
+ <h1 id="datacenters">List of all datacenters</h1>
78
+ <ul>
79
+ <% datacenters.each do |dc| %>
80
+ <li id="dc_<%=dc %>%"><%= dc %> with <%= services(dc:dc).keys.count %> services, <%= nodes(dc:dc).count %> nodes</li>
81
+ <% end %>
82
+ </ul>
83
+ <h1 id="services">List all services instances sorted by node name</h1>
84
+ <% services.each do |service_name, tags|
85
+ service_group = service(service_name)
86
+ nodes_sorted = service_group.sort {|a,b| a['Node']['Node'] <=> b['Node']['Node'] }
87
+ passing, warning, critical, sum = compute_service_status(nodes_sorted)
88
+ service_state = 'passing'
89
+ service_state = 'warning' if warning > 0
90
+ service_state = 'critical' if critical > 0
91
+ %>
92
+ <div class="service <%= service_state %>" id="service_<%= service_name %>">
93
+ <h2 class="text-<%= status_to_class(service_state) %>" title="<%= tags.join(', ') %>"><%= service_name %><a class="qlink" href="#service_<%= service_name %>">&#128279;</a></h2>
94
+ <% if sum > 0
95
+ %><div class="progress">
96
+ <div title="<%= "#{passing} / #{sum}" %>" class="progress-bar bg-success" role="progressbar" style="width:<%= (100.0 * passing / sum).round(2) %>%" aria-valuenow="<%= passing %>" aria-valuemin="0" aria-valuemax="<%= sum %>"></div>
97
+ <div title="<%= "#{warning} / #{sum}" %>" class="progress-bar bg-warning" role="progressbar" style="width:<%= (100.0 * warning / sum).round(2) %>%" aria-valuenow="<%= warning %>" aria-valuemin="0" aria-valuemax="<%= sum %>"></div>
98
+ <div title="<%= "#{critical} / #{sum}" %>" class="progress-bar bg-danger" role="progressbar" style="width:<%= (100.0 * critical / sum).round(2) %>%" aria-valuenow="<%= critical %>" aria-valuemin="0" aria-valuemax="<%= sum %>"></div>
99
+ </div>
100
+ <% end %>
101
+ <div class="list-group">
102
+ <% nodes_sorted.each do |snode|
103
+ tags = snode['Service']['Tags'].sort
104
+ addr = snode['Node']['Address']
105
+ port_num = snode['Service']['Port'].to_i
106
+ port = port_num && port_num > 0 ? ":#{port_num}" : ''
107
+ url = if tags.include? 'https'
108
+ "https://#{addr}#{port}"
109
+ elsif tags.include? 'http'
110
+ "http://#{addr}#{port}"
111
+ elsif tags.include? 'ftp'
112
+ "ftp://#{addr}#{port}"
113
+ else
114
+ nil
115
+ end
116
+ %><div class="list-group-item check"><a <%= url ? "href=\"#{url}\"" : nil %>><%=
117
+ snode['Node']['Node'] %><%= port %></a>
118
+ <span class="tags"><%
119
+ snode['Service']['Tags'].sort.each do |t|
120
+ %><span class="badge badge-pill"><%= t %></span><%
121
+ end
122
+ %></span>
123
+ <span class="float-right statuses"><%
124
+ snode['Checks'].each do |c| %> <span title="<%= c['Name']%>" class="badge badge-pill badge-<%= status_to_class(c['Status']) %>"><%= c['Status']
125
+ %></span><% end if snode['Checks'] %></span></div>
126
+ <% end %>
127
+ </div>
128
+ </div>
129
+ <% end
130
+ %>
131
+ </main>
132
+ </div>
133
+ </div>
71
134
  <%= render_file 'common/footer.html.erb' %>
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.0.7
4
+ version: 1.0.8
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: 2018-03-16 00:00:00.000000000 Z
11
+ date: 2018-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: em-http-request
@@ -129,7 +129,9 @@ email:
129
129
  executables:
130
130
  - consul-templaterb
131
131
  extensions: []
132
- extra_rdoc_files: []
132
+ extra_rdoc_files:
133
+ - README.md
134
+ - TemplateAPI.md
133
135
  files:
134
136
  - ".gitignore"
135
137
  - ".gitreview"
@@ -142,6 +144,7 @@ files:
142
144
  - LICENSE.txt
143
145
  - README.md
144
146
  - Rakefile
147
+ - TemplateAPI.md
145
148
  - bin/consul-templaterb
146
149
  - consul-templaterb.gemspec
147
150
  - lib/consul/async/consul_endpoint.rb