consult 0.5.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 31a2190f1f1f38f5b2f674fa7d1ed91be5522d335e10c655f05493cbfdc056c3
4
+ data.tar.gz: 0c51acfbd537bb327f6360a4e6ef59fa8ec79b35c7326e421b14362f7a47d827
5
+ SHA512:
6
+ metadata.gz: b68efe09e942c634da159734f792da61e9fe16dc288e95b3cfafa1c0392457998f4149d0a6c5c1748238ff7ac3bf1165e63c80ed2d956914a372e591dbfc1545
7
+ data.tar.gz: 14b46ab1dccee0d9739ec820ee94c89af221aedf83e5ec46f072df6fbe9b78c353cc603cd6274d68c8f5eafecde2adb20653c0ef322881843971d17465b209ef
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/support/rendered/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,34 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4
3
+
4
+ Metrics/LineLength:
5
+ Max: 117
6
+
7
+ Metrics/MethodLength:
8
+ Max: 20
9
+
10
+ Metrics/ClassLength:
11
+ Max: 400
12
+
13
+ Metrics/AbcSize:
14
+ Max: 30
15
+
16
+ Metrics/BlockLength:
17
+ Exclude:
18
+ - 'spec/**/*_spec.rb'
19
+ - 'test/**/*_test.rb'
20
+ - '*.gemspec'
21
+ - 'Gemfile'
22
+ - 'Guardfile'
23
+
24
+ # Reserve space for blocks to visually distinguish blocks from hashes
25
+ Layout/SpaceInsideHashLiteralBraces:
26
+ EnforcedStyle: no_space
27
+
28
+ # use 'raise' instead of 'fail'
29
+ Style/SignalException:
30
+ EnforcedStyle: only_raise
31
+
32
+ # mixed: Use slashes on single-line regexes, and %r on multi-line regexes.
33
+ Style/RegexpLiteral:
34
+ EnforcedStyle: mixed
data/.travis.yml ADDED
@@ -0,0 +1,15 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.0
5
+ - 2.4.1
6
+
7
+ services:
8
+ - docker
9
+
10
+ before_install:
11
+ - gem install bundler -v 1.16.1
12
+ - docker pull consul
13
+ - docker pull vault
14
+ - docker run -d --name=dev-consul -p 8500:8500 consul
15
+ - docker run -d --name=dev-vault -p 8200:8200 --cap-add=IPC_LOCK -e 'VAULT_DEV_ROOT_TOKEN_ID=94e1a9ed-5d72-5677-27ab-ebc485cca368' vault
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in consult.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,106 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ consult (0.5.0)
5
+ activesupport (> 4, < 6)
6
+ diplomat
7
+ vault
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (5.1.4)
13
+ concurrent-ruby (~> 1.0, >= 1.0.2)
14
+ i18n (~> 0.7)
15
+ minitest (~> 5.1)
16
+ tzinfo (~> 1.1)
17
+ byebug (9.1.0)
18
+ coderay (1.1.2)
19
+ concurrent-ruby (1.0.5)
20
+ diff-lcs (1.3)
21
+ diplomat (2.0.2)
22
+ faraday (~> 0.9)
23
+ json
24
+ docile (1.1.5)
25
+ faraday (0.14.0)
26
+ multipart-post (>= 1.2, < 3)
27
+ ffi (1.9.18)
28
+ formatador (0.2.5)
29
+ guard (2.14.1)
30
+ formatador (>= 0.2.4)
31
+ listen (>= 2.7, < 4.0)
32
+ lumberjack (~> 1.0)
33
+ nenv (~> 0.1)
34
+ notiffany (~> 0.0)
35
+ pry (>= 0.9.12)
36
+ shellany (~> 0.0)
37
+ thor (>= 0.18.1)
38
+ guard-compat (1.2.1)
39
+ guard-rspec (4.7.3)
40
+ guard (~> 2.1)
41
+ guard-compat (~> 1.1)
42
+ rspec (>= 2.99.0, < 4.0)
43
+ i18n (0.9.5)
44
+ concurrent-ruby (~> 1.0)
45
+ json (2.1.0)
46
+ listen (3.1.5)
47
+ rb-fsevent (~> 0.9, >= 0.9.4)
48
+ rb-inotify (~> 0.9, >= 0.9.7)
49
+ ruby_dep (~> 1.2)
50
+ lumberjack (1.0.12)
51
+ method_source (0.9.0)
52
+ minitest (5.11.3)
53
+ multipart-post (2.0.0)
54
+ nenv (0.3.0)
55
+ notiffany (0.1.1)
56
+ nenv (~> 0.1)
57
+ shellany (~> 0.0)
58
+ pry (0.11.3)
59
+ coderay (~> 1.1.0)
60
+ method_source (~> 0.9.0)
61
+ rake (10.5.0)
62
+ rb-fsevent (0.10.2)
63
+ rb-inotify (0.9.10)
64
+ ffi (>= 0.5.0, < 2)
65
+ rspec (3.7.0)
66
+ rspec-core (~> 3.7.0)
67
+ rspec-expectations (~> 3.7.0)
68
+ rspec-mocks (~> 3.7.0)
69
+ rspec-core (3.7.0)
70
+ rspec-support (~> 3.7.0)
71
+ rspec-expectations (3.7.0)
72
+ diff-lcs (>= 1.2.0, < 2.0)
73
+ rspec-support (~> 3.7.0)
74
+ rspec-mocks (3.7.0)
75
+ diff-lcs (>= 1.2.0, < 2.0)
76
+ rspec-support (~> 3.7.0)
77
+ rspec-support (3.7.0)
78
+ ruby_dep (1.5.0)
79
+ shellany (0.0.1)
80
+ simplecov (0.15.1)
81
+ docile (~> 1.1.0)
82
+ json (>= 1.8, < 3)
83
+ simplecov-html (~> 0.10.0)
84
+ simplecov-html (0.10.2)
85
+ thor (0.20.0)
86
+ thread_safe (0.3.6)
87
+ tzinfo (1.2.5)
88
+ thread_safe (~> 0.1)
89
+ vault (0.10.1)
90
+
91
+ PLATFORMS
92
+ ruby
93
+
94
+ DEPENDENCIES
95
+ bundler (~> 1.16)
96
+ byebug
97
+ consult!
98
+ guard
99
+ guard-rspec
100
+ pry
101
+ rake (~> 10.0)
102
+ rspec (~> 3.0)
103
+ simplecov
104
+
105
+ BUNDLED WITH
106
+ 1.16.1
data/Guardfile ADDED
@@ -0,0 +1,42 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: 'bundle exec rspec' do
28
+ require 'guard/rspec/dsl'
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Jeff Fraser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Procfile ADDED
@@ -0,0 +1,3 @@
1
+ consul: consul agent -dev
2
+ vault: vault server -dev -dev-root-token-id=94e1a9ed-5d72-5677-27ab-ebc485cca368
3
+ #guard: bundle exec guard
data/README.md ADDED
@@ -0,0 +1,289 @@
1
+ # Consult
2
+
3
+ Render configuration and secrets from [Consul] and [Vault] with ERB templates.
4
+
5
+ This gem is a spiritual sibling to [Consul Template], but specifically intended for use in Ruby or Rails environments. It does not have the same features as Consul Template; it is intended for simpler scenarios. Most importantly, leases and configuration changes are _not_ watched to automatically re-render. Consult is intended for more static or medium-to-long lived configuration.
6
+
7
+ If this gem is included in a Rails, the template will render on Rails boot. Because of this, configuration or credential changes can be picked up by restarting your app.
8
+
9
+ This gem is considered _beta_. At Veracross, we are just beginning to use it in staging environments.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'consult'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install consult
26
+
27
+ ## Usage
28
+
29
+ Using Consult requires a configuration YAML file and a series of templates. The configuration file serves as a manifest of templates and their settings, along with optional connection settings to Vault and Consul.
30
+
31
+ ### Configuration
32
+
33
+ ```yaml
34
+ # The environment you are operating it. Defaults to ENV['RAILS_ENV'] or Rails.env if Rails is present.
35
+ env: test
36
+
37
+ # Optional
38
+ consul:
39
+ # Prefers `CONSUL_HTTP_ADDR` environment variable
40
+ address: http://0.0.0.0:8500
41
+ # Prefers `CONSUL_HTTP_TOKEN` environment variable, or a ~/.consul-token file.
42
+ # Setting a token here is not best practice because consul tokens should have a relatively short TTL
43
+ # and be read from the environment, but this is convenient for testing.
44
+ token: 5d3f1c66-d405-4ad1-b634-ea30be4fb539
45
+
46
+ # Optional
47
+ vault:
48
+ # Prefers `VAULT_ADDR` environment variable
49
+ address: http://0.0.0.0:8200
50
+ # Prefers `VAULT_TOKEN` environment variable, or a ~/.vault-token file
51
+ # Setting a token here is not best practice because vault tokens should have a relatively short TTL
52
+ # and be read from the environment, but this is convenient for testing.
53
+ token: 8fcd5aed-3eb9-412d-8923-1397af7aede2
54
+
55
+ # Enumerate the templates.
56
+ templates:
57
+ database:
58
+ # Relative paths are assumed to be in #{Rails.root}/config.
59
+ # Path to the template
60
+ path: templates/database.yml.erb
61
+ # Destination for the rendered template
62
+ dest: rendered/database.yml
63
+ # Which environments to render this template in
64
+ environments: all
65
+ # If the file is less than this old, do not re-render
66
+ ttl: 3600 # seconds
67
+
68
+ secrets:
69
+ path: templates/secrets.yml.erb
70
+ dest: rendered/secrets.yml
71
+ environments: test
72
+
73
+ should_be_excluded:
74
+ path: templates/fake.yml.erb
75
+ dest: rendered/fake.yml
76
+ environments: production # won't be rendered because it doesn't match `env` at the top
77
+ ```
78
+
79
+ ### Conventions
80
+
81
+ Because you can use this to fetch secrets or render out things like `database.yml`, you should delete `secrets.yml` and `database.yml` from your app and add them to `.gitignore`. Only keep your templates in source control.
82
+
83
+ ### Templates
84
+
85
+ Templates are ERB files, and as such can do anything ERB can do. However, Consult does provide a few helper functions.
86
+
87
+ Note that under the hood, Consult is using [Diplomat](https://github.com/WeAreFarmGeek/diplomat) and the [Vault Gem](https://github.com/hashicorp/vault-ruby). Consul objects are therefore Diplomat objects, and likewise Vault objects are Vault Gem objects. See their API docs for more information. Diplomat generally returns structs with title cased properties.
88
+
89
+ #### Consul Functions
90
+
91
+ **service(name)** - Fetch the nodes for the specified service.
92
+
93
+ ```yaml
94
+ <% service("redis").each do |node| %>
95
+ host: <%= node.Address %>
96
+ port: <%= node.ServicePort %>
97
+ <% end %>
98
+ ```
99
+
100
+ returns
101
+
102
+ host: redis1.local
103
+ port: 6379
104
+
105
+ **query(name_or_id, options: nil)** - Execute the specified prepared Query by name or ID
106
+
107
+ ```ruby
108
+ <% query('pg-production').tap do |result| %>
109
+ service: <%= result.Service %>
110
+ nodes:
111
+ <% result.Nodes.each do |node| %>
112
+ address: <%= node['Node']['Address']
113
+ <% end %>
114
+ <% end %>
115
+ ```
116
+
117
+ **query_nodes(name_or_id, options: nil)** - Return only the nodes from a prepared query
118
+
119
+ ```yml
120
+ <% query_nodes('pg-production').each do |node| %>
121
+ <%= node['Node'] %>:
122
+ host: <%= node['Address'] %>
123
+ datacenter: <%= node['Datacenter'] %>
124
+ <% end %>
125
+ ```
126
+
127
+ pg1:
128
+ host: 10.0.100.101
129
+ datacenter: us-east-1
130
+ pg2:
131
+ host: 10.0.100.102
132
+ datacenter: us-east-2
133
+
134
+ #### Vault Functions
135
+
136
+ **secret(path)** - Fetch a secret at the given path.
137
+
138
+ username: <%= secret('secret/credentials').data[:username] %>
139
+
140
+ yields
141
+
142
+ username: kylo.ren
143
+
144
+ **secrets(path)** - List all secrets at the given path
145
+
146
+ ```ruby
147
+ <% secrets('secret').each do |path| %>
148
+ <%= path %>
149
+ <% end %>
150
+ ```
151
+
152
+ yields
153
+
154
+ foo
155
+ bar
156
+ baz
157
+
158
+ #### Utility Functions
159
+
160
+ **timestamp** - Renders the current utc timestamp.
161
+
162
+ <%= timestamp %>
163
+
164
+ renders
165
+
166
+ 2018-02-23 14:20:29 UTC
167
+
168
+ **indent(string, level, separator = '\n')** - Indents a multi-line string by `level`
169
+
170
+ ```yml
171
+ keys:
172
+ multi_line: |
173
+ <%= indent secret('secret/keys/multi_line).data[:value], 4 %>
174
+ ```
175
+
176
+ renders
177
+
178
+ ```yml
179
+ keys:
180
+ multi_line: |
181
+ 30ada39cccf79aadbd1d870bc15f0086
182
+ 7ea8d734e81e9c6710faa15b0aff516c
183
+ 27778ab3b1e10db2028352f12c3c07bb
184
+ e7ec40d1e45834681b4dc3548230d1ca
185
+ ```
186
+
187
+ **with(whatever)** - takes `whatever` and yields it back. Equivalent to `tap`, but provided as a bridge from [Consul Template]/Go template conventions.
188
+
189
+ ```yml
190
+ <% with secret "secrets/credentials" do |s| %>
191
+ username: <%= s.data[:username] %>
192
+ password: <%= s.data[:password] %>
193
+ <% end %>
194
+ ```
195
+
196
+ #### More Full Examples
197
+
198
+ Render multiple servers into a database.yml file, keyed by their name.
199
+
200
+ ```yml
201
+ # database.yml.erb
202
+ <% service("postgres").each do |node| %>
203
+ '<%= node.Node %>':
204
+ host: <%= node.Address %>
205
+ port: <%= node.ServicePort %>
206
+ <%- with secret "secret/base/sql-server/#{node.Node}/web" do |s| -%>
207
+ # Credential lease good until <%= (timestamp + s.lease_duration).to_s %>
208
+ username: <%= s.data[:username] %>
209
+ password: <%= s.data[:password] %>
210
+ <% end -%>
211
+ <% end %>
212
+ ```
213
+
214
+ Yields something like
215
+
216
+ ```yml
217
+ # database.yml
218
+ 'db1':
219
+ host: 10.0.100.101
220
+ port: 5432
221
+ # Credential lease good until 2018-02-24 16:08:29 UTC
222
+ username: foo
223
+ password: bar
224
+ 'db2':
225
+ host: 10.0.100.102
226
+ port: 5432
227
+ # Credential lease good until 2018-02-24 16:08:29 UTC
228
+ username: baz
229
+ password: qux
230
+ ```
231
+
232
+ #### Secrets
233
+
234
+ ```yml
235
+ # secrets.yml.erb
236
+ shared:
237
+ rollbar_token: <%= secret('secrets/third_party').data[:rollbar] %>
238
+ scout_token: <%= secret('secrets/third_party').data[:scout] %>
239
+
240
+ development:
241
+ secret_key_base: abcd1234....
242
+
243
+ production:
244
+ secret_key_base: <%= secret('secret/apps/myapp').data[:secret_key_base] %>
245
+ ```
246
+
247
+ Then reference secrets in your app with `Rails.application.secrets`.
248
+
249
+ ```ruby
250
+ # config/intiializers/rollbar.rb
251
+ Rollbar.configure do |config|
252
+ config.access_token = Rails.application.secrets.rollbar_token
253
+ end
254
+ ```
255
+
256
+ ## Why we built this
257
+
258
+ We use [Consul Template] for server-level configuration, but application level configuration is more tricky. It is difficult to solve the problem of fetching secrets and config in a consistent way in both development and production. We wanted to avoid having [Consul Template] in use in production, but some other custom solution in development.
259
+
260
+ Using this gem, the implementation is the same in dev and prod, and it is frictionless since the templates render when Rails boots.
261
+
262
+ ## Development
263
+
264
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment. See below for testing instructions.
265
+
266
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
267
+
268
+ ### Testing
269
+
270
+ Testing is easiest by running Consul and Vault in Docker. Just boot up their minimal containers:
271
+
272
+ $ docker pull consul
273
+ $ docker pull vault
274
+ $ docker run -d --name=dev-consul -p 8500:8500 consul
275
+ $ docker run -d --name=dev-vault -p 8200:8200 --cap-add=IPC_LOCK -e 'VAULT_DEV_ROOT_TOKEN_ID=94e1a9ed-5d72-5677-27ab-ebc485cca368' vault
276
+
277
+ Then run `bundle exec rspec`, or `bundle exec guard`.
278
+
279
+ ## Contributing
280
+
281
+ Bug reports and pull requests are welcome on GitHub at https://github.com/veracross/consult.
282
+
283
+ ## License
284
+
285
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
286
+
287
+ [Vault]: https://github.com/hashicorp/vault
288
+ [Consul]: https://github.com/hashicorp/consul
289
+ [Consul Template]: https://github.com/hashicorp/consul-template
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'consult'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+ Consult.load config_dir: 'spec/support'
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ require 'pry'
12
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+ # Do any other automated setup that you need to do here
data/consult.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'consult/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'consult'
9
+ spec.version = Consult::VERSION
10
+ spec.authors = ['Jeff Fraser']
11
+ spec.email = ['jeff.fraser@veracross.com']
12
+
13
+ spec.summary = 'Manage consul/vault backed template files in Ruby.'
14
+ spec.description = 'Manage consul/vault backed template files in Ruby.'
15
+ spec.homepage = 'https://github.com/veracross/consult'
16
+ spec.license = 'MIT'
17
+
18
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
19
+
20
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
21
+ f.match(%r{^(test|spec|features)/})
22
+ end
23
+ spec.bindir = 'exe'
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ['lib']
26
+
27
+ spec.add_dependency 'activesupport', '> 4', '< 6'
28
+ spec.add_dependency 'diplomat'
29
+ spec.add_dependency 'vault'
30
+
31
+ spec.add_development_dependency 'bundler', '~> 1.16'
32
+ spec.add_development_dependency 'byebug'
33
+ spec.add_development_dependency 'guard'
34
+ spec.add_development_dependency 'guard-rspec'
35
+ spec.add_development_dependency 'pry'
36
+ spec.add_development_dependency 'rake', '~> 10.0'
37
+ spec.add_development_dependency 'rspec', '~> 3.0'
38
+ spec.add_development_dependency 'simplecov'
39
+ end
data/lib/consult.rb ADDED
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'yaml'
5
+ require 'active_support/core_ext/hash'
6
+ require 'erb'
7
+ require 'vault'
8
+ require 'diplomat'
9
+
10
+ require 'consult/version'
11
+ require 'consult/utilities'
12
+ require 'consult/template'
13
+
14
+ module Consult
15
+ @config = {}
16
+ @templates = []
17
+
18
+ CONSUL_DISK_TOKEN = Pathname.new("#{Dir.home}/.consul-token").freeze
19
+
20
+ class << self
21
+ attr_reader :config, :templates
22
+
23
+ def load(config_dir: nil)
24
+ root directory: config_dir
25
+ yaml = root.join('config', 'consult.yml')
26
+ @config = yaml.exist? ? YAML.safe_load(ERB.new(yaml.read).result, [], [], true) : {}
27
+ @config.deep_symbolize_keys!
28
+ @templates = @config[:templates]&.map { |name, config| Template.new(name, config) }
29
+
30
+ configure_consul
31
+ configure_vault
32
+ end
33
+
34
+ def configure_consul
35
+ @config[:consul] ||= {}
36
+
37
+ # We map conventional `address` and `token` params to Diplomat's unconventional `url` and `acl_token` settings
38
+ # Additionally: prefer env vars over explicit config
39
+ configured_address = @config[:consul].delete(:address)
40
+ @config[:consul][:url] = ENV['CONSUL_HTTP_ADDR'] || configured_address || @config[:consul][:url]
41
+ @config[:consul][:acl_token] = consul_token
42
+
43
+ Diplomat.configure do |c|
44
+ @config[:consul].each do |opt, val|
45
+ c.send "#{opt}=".to_sym, val
46
+ end
47
+ end
48
+ end
49
+
50
+ def configure_vault
51
+ return unless @config.key? :vault
52
+
53
+ Vault.configure do |c|
54
+ @config[:vault].each do |opt, val|
55
+ c.send "#{opt}=".to_sym, val
56
+ end
57
+ end
58
+ end
59
+
60
+ def root(directory: nil)
61
+ @_root ||= directory ? Pathname.new(directory) : (!!defined?(Rails) && ::Rails.root)
62
+ end
63
+
64
+ def env
65
+ @config[:env] || ENV['RAILS_ENV'] || Rails.env
66
+ end
67
+
68
+ # Return only the templates that are relevant for the current environment
69
+ def active_templates
70
+ templates.select(&:should_render?)
71
+ end
72
+
73
+ # Render templates.
74
+ def render!
75
+ active_templates.each(&:render)
76
+ end
77
+
78
+ # Map more conventional `token` parameter to Diplomat's `acl_token` configuration.
79
+ # Additionally, we support ~/.consul-token, similar to Vault's support for ~/.vault-token
80
+ def consul_token
81
+ ENV['CONSUL_HTTP_TOKEN'] ||
82
+ @config[:consul].delete(:token) ||
83
+ @config[:consul][:acl_token] ||
84
+ CONSUL_DISK_TOKEN.read.chomp
85
+ end
86
+ end
87
+ end
88
+
89
+ require 'consult/rails/engine' if defined?(Rails)
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Consult
4
+ module Rails
5
+ # When using Rails, this will render your templates
6
+ # on app boot.
7
+ class Railtie < ::Rails::Railtie
8
+ config.before_configuration do
9
+ Consult.load
10
+ Consult.render!
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'consult/template_functions'
4
+
5
+ module Consult
6
+ class Template
7
+ include Utilities
8
+ include TemplateFunctions
9
+
10
+ attr_reader :name, :config
11
+
12
+ def initialize(name, config)
13
+ @name = name
14
+ @config = config
15
+ end
16
+
17
+ def render(save: true)
18
+ renderer = ERB.new(File.read(path, encoding: 'utf-8'), nil, '-')
19
+ result = renderer.result(binding)
20
+
21
+ File.open(dest, 'w') { |f| f << result } if save
22
+ result
23
+ rescue StandardError => e
24
+ puts "Error rendering template: #{name}"
25
+ raise e
26
+ end
27
+
28
+ def path
29
+ resolve @config.fetch(:path)
30
+ end
31
+
32
+ def dest
33
+ resolve @config.fetch(:dest)
34
+ end
35
+
36
+ def should_render?
37
+ (@config[:environments] == 'all' || @config[:environments].include?(Consult.env)) && expired?
38
+ end
39
+
40
+ def expired?
41
+ # Treat renders as expired if a TTL isn't set, or it has never been rendered before
42
+ return true if !config.key?(:ttl) || !dest.exist?
43
+ dest.mtime < (Time.now - @config[:ttl].to_i)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Consult
4
+ module TemplateFunctions
5
+ ############
6
+ # Vault
7
+ ############
8
+ def secret(path)
9
+ Vault.logical.read(path)
10
+ end
11
+
12
+ def secrets(path)
13
+ Vault.logical.list(path)
14
+ end
15
+
16
+ ############
17
+ # Consul
18
+ ############
19
+ def service(key, scope: :all, options: nil, meta: nil)
20
+ Diplomat::Service.get(key, scope, options, meta)
21
+ end
22
+
23
+ # Execute a prepared query
24
+ def query(name_or_id, options: nil)
25
+ Diplomat::Query.execute(name_or_id, options)
26
+ end
27
+
28
+ # Return just the nodes from a prepared query
29
+ def query_nodes(*args)
30
+ query(*args)&.Nodes&.map { |node| node['Node'] }
31
+ end
32
+
33
+ ############
34
+ # Utility
35
+ ############
36
+ # Provided as a bridge to consul-template/go conventions. Simply yields
37
+ # back whatever was provided.
38
+ def with(whatever)
39
+ yield whatever
40
+ end
41
+
42
+ def timestamp
43
+ Time.now.utc
44
+ end
45
+
46
+ # Indent a multi-line string by the provided amount.
47
+ def indent(string, level, separator = "\n")
48
+ string.split(separator).map do |line|
49
+ ' ' * level + line
50
+ end.join(separator)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Consult
4
+ module Utilities
5
+ def resolve(path)
6
+ pathname = Pathname.new(path)
7
+ pathname.relative? ? Consult.root.join(pathname) : pathname
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module Consult
2
+ VERSION = '0.5.0'
3
+ end
metadata ADDED
@@ -0,0 +1,225 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: consult
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeff Fraser
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-02-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">"
18
+ - !ruby/object:Gem::Version
19
+ version: '4'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '6'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">"
28
+ - !ruby/object:Gem::Version
29
+ version: '4'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '6'
33
+ - !ruby/object:Gem::Dependency
34
+ name: diplomat
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: vault
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: bundler
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.16'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.16'
75
+ - !ruby/object:Gem::Dependency
76
+ name: byebug
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: guard
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: guard-rspec
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: pry
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: rake
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '10.0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '10.0'
145
+ - !ruby/object:Gem::Dependency
146
+ name: rspec
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '3.0'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '3.0'
159
+ - !ruby/object:Gem::Dependency
160
+ name: simplecov
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ description: Manage consul/vault backed template files in Ruby.
174
+ email:
175
+ - jeff.fraser@veracross.com
176
+ executables: []
177
+ extensions: []
178
+ extra_rdoc_files: []
179
+ files:
180
+ - ".gitignore"
181
+ - ".rspec"
182
+ - ".rubocop.yml"
183
+ - ".travis.yml"
184
+ - Gemfile
185
+ - Gemfile.lock
186
+ - Guardfile
187
+ - LICENSE.txt
188
+ - Procfile
189
+ - README.md
190
+ - Rakefile
191
+ - bin/console
192
+ - bin/setup
193
+ - consult.gemspec
194
+ - lib/consult.rb
195
+ - lib/consult/rails/engine.rb
196
+ - lib/consult/template.rb
197
+ - lib/consult/template_functions.rb
198
+ - lib/consult/utilities.rb
199
+ - lib/consult/version.rb
200
+ homepage: https://github.com/veracross/consult
201
+ licenses:
202
+ - MIT
203
+ metadata:
204
+ allowed_push_host: https://rubygems.org
205
+ post_install_message:
206
+ rdoc_options: []
207
+ require_paths:
208
+ - lib
209
+ required_ruby_version: !ruby/object:Gem::Requirement
210
+ requirements:
211
+ - - ">="
212
+ - !ruby/object:Gem::Version
213
+ version: '0'
214
+ required_rubygems_version: !ruby/object:Gem::Requirement
215
+ requirements:
216
+ - - ">="
217
+ - !ruby/object:Gem::Version
218
+ version: '0'
219
+ requirements: []
220
+ rubyforge_project:
221
+ rubygems_version: 2.7.3
222
+ signing_key:
223
+ specification_version: 4
224
+ summary: Manage consul/vault backed template files in Ruby.
225
+ test_files: []