consult 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +34 -0
- data/.travis.yml +15 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +106 -0
- data/Guardfile +42 -0
- data/LICENSE.txt +21 -0
- data/Procfile +3 -0
- data/README.md +289 -0
- data/Rakefile +6 -0
- data/bin/console +12 -0
- data/bin/setup +7 -0
- data/consult.gemspec +39 -0
- data/lib/consult.rb +89 -0
- data/lib/consult/rails/engine.rb +14 -0
- data/lib/consult/template.rb +46 -0
- data/lib/consult/template_functions.rb +53 -0
- data/lib/consult/utilities.rb +10 -0
- data/lib/consult/version.rb +3 -0
- metadata +225 -0
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
data/.rspec
ADDED
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
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
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
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
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
|
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: []
|