leda 0.0.1
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 +7 -0
- data/.gitignore +15 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +91 -0
- data/Rakefile +2 -0
- data/leda.gemspec +29 -0
- data/lib/leda.rb +37 -0
- data/lib/leda/capistrano.rb +84 -0
- data/lib/leda/configuration.rb +91 -0
- data/lib/leda/data_unit.rb +13 -0
- data/lib/leda/rake.rb +67 -0
- data/lib/leda/runner.rb +71 -0
- data/lib/leda/store.rb +53 -0
- data/lib/leda/stores/elasticsearch.rb +190 -0
- data/lib/leda/stores/postgresql.rb +134 -0
- data/lib/leda/version.rb +3 -0
- data/spec/leda/configuration_spec.rb +101 -0
- data/spec/leda/rake_spec.rb +86 -0
- data/spec/leda/runner_spec.rb +194 -0
- data/spec/leda/version_spec.rb +11 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/recording_store.rb +29 -0
- data/spec/support/tmpdir.rb +18 -0
- metadata +145 -0
data/lib/leda/version.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Leda
|
4
|
+
describe Configuration do
|
5
|
+
let(:configuration) { Configuration.new }
|
6
|
+
|
7
|
+
describe 'defined from DSL' do
|
8
|
+
let(:actual) do
|
9
|
+
Configuration.new do |leda|
|
10
|
+
leda.data_unit 'providers' do |c|
|
11
|
+
c.postgresql tables: %w(practices offices practitioners)
|
12
|
+
c.elasticsearch indexes: %w(sst-practitioners)
|
13
|
+
end
|
14
|
+
|
15
|
+
leda.data_unit 'groups' do |c|
|
16
|
+
c.postgresql tables: %w(groups mentioned practitioners)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'produces the expected data units' do
|
22
|
+
expect(actual.data_units.map(&:name)).to eq(%w(providers groups))
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'produces appropriate stores for each data unit' do
|
26
|
+
providers_data_unit = actual.data_units.first
|
27
|
+
|
28
|
+
expect(providers_data_unit.stores.map(&:class)).
|
29
|
+
to eq([Stores::Postgresql, Stores::Elasticsearch])
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'passes the configuration parameters to each store' do
|
33
|
+
providers_postgresql_store = actual.data_units.first.stores.first
|
34
|
+
|
35
|
+
expect(providers_postgresql_store.options).to eq({ tables: %w(practices offices practitioners) })
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#update' do
|
40
|
+
it 'adds to the existing configuration' do
|
41
|
+
configuration.update do |leda|
|
42
|
+
leda.data_unit 'lookups' do |d|
|
43
|
+
d.postgresql tables: %w(zip_codes)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
configuration.update do |leda|
|
48
|
+
leda.data_unit 'people' do |d|
|
49
|
+
d.postgresql tables: %w(people phone_numbers street_addresses)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
expect(configuration.data_units.map(&:name)).to eq(%w(lookups people))
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns the configuration' do
|
57
|
+
expect(configuration.update { }).to eql(configuration)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#project_root_dir=' do
|
62
|
+
it 'coerces a string into a Pathname' do
|
63
|
+
configuration.project_root_dir = '/foo/quux'
|
64
|
+
|
65
|
+
expect(configuration.project_root_dir).to eq(Pathname.new('/foo/quux'))
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'leaves a Pathname alone' do
|
69
|
+
configuration.project_root_dir = Pathname.new('/bar/baz')
|
70
|
+
expect(configuration.project_root_dir).to eq(Pathname.new('/bar/baz'))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#base_path' do
|
75
|
+
it 'defaults to db/leda' do
|
76
|
+
expect(configuration.base_path).to eq(Pathname.new('db/leda'))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#base_path=' do
|
81
|
+
it 'coerces a string into a Pathname' do
|
82
|
+
configuration.base_path = 'foo/quux'
|
83
|
+
|
84
|
+
expect(configuration.base_path).to eq(Pathname.new('foo/quux'))
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'leaves a Pathname alone' do
|
88
|
+
configuration.base_path = Pathname.new('bar/baz')
|
89
|
+
expect(configuration.base_path).to eq(Pathname.new('bar/baz'))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#base_dir' do
|
94
|
+
it 'is the combination of the project root and the base path' do
|
95
|
+
configuration.project_root_dir = '/app-root'
|
96
|
+
configuration.base_path = 'db-data'
|
97
|
+
expect(configuration.base_dir).to eq(Pathname.new('/app-root/db-data'))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
module Leda
|
5
|
+
describe Rake do
|
6
|
+
let(:configuration) {
|
7
|
+
Configuration.new do |leda|
|
8
|
+
leda.data_unit 'tlus' do |du|
|
9
|
+
du.mock_c
|
10
|
+
end
|
11
|
+
|
12
|
+
leda.data_unit 'people' do |du|
|
13
|
+
du.mock_a
|
14
|
+
du.mock_b
|
15
|
+
end
|
16
|
+
end
|
17
|
+
}
|
18
|
+
|
19
|
+
before do
|
20
|
+
::Rake.application = ::Rake::Application.new
|
21
|
+
Leda::Rake.define_tasks(configuration, [:outside_prereq])
|
22
|
+
end
|
23
|
+
|
24
|
+
after do
|
25
|
+
::Rake.application.clear
|
26
|
+
end
|
27
|
+
|
28
|
+
shared_examples 'a Leda task' do
|
29
|
+
it 'exists' do
|
30
|
+
expect { task }.to_not raise_error
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has a description' do
|
34
|
+
expect(task.comment).not_to be_nil
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'depends on the supplied prereqs' do
|
38
|
+
expect(task.prerequisites).to eq(%w(outside_prereq))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
shared_examples 'a restore_from task' do
|
43
|
+
it_behaves_like 'a Leda task'
|
44
|
+
|
45
|
+
it 'has a source_env argument' do
|
46
|
+
expect(task.arg_names).to eq([:source_env])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'the all:dump task' do
|
51
|
+
let(:task) { ::Rake::Task['dump'] }
|
52
|
+
|
53
|
+
it_behaves_like 'a Leda task'
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'an individual unit dump task' do
|
57
|
+
let(:task) { ::Rake::Task['people:dump'] }
|
58
|
+
|
59
|
+
it_behaves_like 'a Leda task'
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'an individual store dump task' do
|
63
|
+
let(:task) { ::Rake::Task['tlus:mock_c:dump'] }
|
64
|
+
|
65
|
+
it_behaves_like 'a Leda task'
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'the all:restore_from task' do
|
69
|
+
let(:task) { ::Rake::Task['restore_from'] }
|
70
|
+
|
71
|
+
it_behaves_like 'a restore_from task'
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'an individual unit restore_from task' do
|
75
|
+
let(:task) { ::Rake::Task['tlus:restore_from'] }
|
76
|
+
|
77
|
+
it_behaves_like 'a restore_from task'
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'an individual store restore_from task' do
|
81
|
+
let(:task) { ::Rake::Task['people:mock_b:restore_from'] }
|
82
|
+
|
83
|
+
it_behaves_like 'a restore_from task'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Leda
|
4
|
+
describe Runner do
|
5
|
+
let(:root) { spec_tmpdir }
|
6
|
+
let(:root_s) { root.to_s }
|
7
|
+
|
8
|
+
let(:configuration) {
|
9
|
+
Configuration.new do |leda|
|
10
|
+
leda.project_root_dir = root
|
11
|
+
leda.base_path = 'base'
|
12
|
+
|
13
|
+
leda.data_unit 'tlus' do |du|
|
14
|
+
du.mock_c
|
15
|
+
du.mock_b
|
16
|
+
end
|
17
|
+
|
18
|
+
leda.data_unit 'people' do |du|
|
19
|
+
du.mock_a
|
20
|
+
du.mock_b
|
21
|
+
end
|
22
|
+
end
|
23
|
+
}
|
24
|
+
|
25
|
+
let(:runner) { Runner.new('dev', configuration) }
|
26
|
+
|
27
|
+
describe '#dump' do
|
28
|
+
let(:actual_calls) {
|
29
|
+
calls = []
|
30
|
+
configuration.data_units.each do |data_unit|
|
31
|
+
data_unit.stores.each do |store|
|
32
|
+
unless store.dump_directories.empty?
|
33
|
+
calls << [data_unit.name, store.name, store.dump_directories.map(&:to_s)]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
calls
|
38
|
+
}
|
39
|
+
|
40
|
+
describe 'with no args' do
|
41
|
+
before do
|
42
|
+
runner.dump
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'runs dump on every configured store' do
|
46
|
+
expect(actual_calls).to eq([
|
47
|
+
['tlus', 'mock_c', [root_s + '/base/dev/tlus/mock_c']],
|
48
|
+
['tlus', 'mock_b', [root_s + '/base/dev/tlus/mock_b']],
|
49
|
+
['people', 'mock_a', [root_s + '/base/dev/people/mock_a']],
|
50
|
+
['people', 'mock_b', [root_s + '/base/dev/people/mock_b']]
|
51
|
+
])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'with just a data unit name' do
|
56
|
+
before do
|
57
|
+
runner.dump('people')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'runs dump on every store in that unit' do
|
61
|
+
expect(actual_calls).to eq([
|
62
|
+
['people', 'mock_a', [root_s + '/base/dev/people/mock_a']],
|
63
|
+
['people', 'mock_b', [root_s + '/base/dev/people/mock_b']]
|
64
|
+
])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'with just a store name' do
|
69
|
+
before do
|
70
|
+
runner.dump(nil, 'mock_b')
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'runs dump on every store of that type' do
|
74
|
+
expect(actual_calls).to eq([
|
75
|
+
['tlus', 'mock_b', [root_s + '/base/dev/tlus/mock_b']],
|
76
|
+
['people', 'mock_b', [root_s + '/base/dev/people/mock_b']]
|
77
|
+
])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'with both a data unit and a store name' do
|
82
|
+
before do
|
83
|
+
runner.dump('tlus', 'mock_b')
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'runs dump on just that combination' do
|
87
|
+
expect(actual_calls).to eq([
|
88
|
+
['tlus', 'mock_b', [root_s + '/base/dev/tlus/mock_b']]
|
89
|
+
])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe 'with an unknown data unit' do
|
94
|
+
it 'throws an exception' do
|
95
|
+
expect { runner.dump('frogs', 'mock_a') }.to raise_error(/No data configured that matches frogs:mock_a/)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe 'with an unused or unknown store' do
|
100
|
+
it 'throws an exception' do
|
101
|
+
expect { runner.dump('people', 'mock_c') }.to raise_error(/No data configured that matches people:mock_c/)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '#dump_relative_paths' do
|
107
|
+
it 'returns the set of paths for the selected parameters' do
|
108
|
+
expect(runner.dump_relative_paths('people').map(&:to_s)).to eq([
|
109
|
+
'base/dev/people/mock_a',
|
110
|
+
'base/dev/people/mock_b'
|
111
|
+
])
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#restore_from' do
|
116
|
+
let(:actual_calls) {
|
117
|
+
calls = []
|
118
|
+
configuration.data_units.each do |data_unit|
|
119
|
+
data_unit.stores.each do |store|
|
120
|
+
unless store.restore_from_directories.empty?
|
121
|
+
calls << [data_unit.name, store.name, store.restore_from_directories.map(&:to_s)]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
calls
|
126
|
+
}
|
127
|
+
|
128
|
+
describe 'with no args' do
|
129
|
+
before do
|
130
|
+
runner.restore_from('prod')
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'runs restore_from on every configured store' do
|
134
|
+
expect(actual_calls).to eq([
|
135
|
+
['tlus', 'mock_c', [root_s + '/base/prod/tlus/mock_c']],
|
136
|
+
['tlus', 'mock_b', [root_s + '/base/prod/tlus/mock_b']],
|
137
|
+
['people', 'mock_a', [root_s + '/base/prod/people/mock_a']],
|
138
|
+
['people', 'mock_b', [root_s + '/base/prod/people/mock_b']]
|
139
|
+
])
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe 'with just a data unit name' do
|
144
|
+
before do
|
145
|
+
runner.restore_from('stg', 'people')
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'runs restore_from on every store in that unit' do
|
149
|
+
expect(actual_calls).to eq([
|
150
|
+
['people', 'mock_a', [root_s + '/base/stg/people/mock_a']],
|
151
|
+
['people', 'mock_b', [root_s + '/base/stg/people/mock_b']]
|
152
|
+
])
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe 'with just a store name' do
|
157
|
+
before do
|
158
|
+
runner.restore_from('local', nil, 'mock_b')
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'runs restore_from on every store of that type' do
|
162
|
+
expect(actual_calls).to eq([
|
163
|
+
['tlus', 'mock_b', [root_s + '/base/local/tlus/mock_b']],
|
164
|
+
['people', 'mock_b', [root_s + '/base/local/people/mock_b']]
|
165
|
+
])
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe 'with both a data unit and a store name' do
|
170
|
+
before do
|
171
|
+
runner.restore_from('super-prod', 'tlus', 'mock_b')
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'runs restore_from on just that combination' do
|
175
|
+
expect(actual_calls).to eq([
|
176
|
+
['tlus', 'mock_b', [root_s + '/base/super-prod/tlus/mock_b']]
|
177
|
+
])
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe 'with an unknown data unit' do
|
182
|
+
it 'throws an exception' do
|
183
|
+
expect { runner.restore_from('prod', 'frogs', 'mock_a') }.to raise_error(/No data configured that matches frogs:mock_a/)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe 'with an unused or unknown store' do
|
188
|
+
it 'throws an exception' do
|
189
|
+
expect { runner.restore_from('prod', 'people', 'mock_c') }.to raise_error(/No data configured that matches people:mock_c/)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
4
|
+
require 'leda'
|
5
|
+
|
6
|
+
Dir[File.expand_path('../support/*.rb', __FILE__)].each do |support|
|
7
|
+
require support
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.include Leda::Spec::Tmpdir
|
12
|
+
|
13
|
+
config.after { rm_tmpdir }
|
14
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class RecordingStore
|
2
|
+
include Leda::Store
|
3
|
+
|
4
|
+
def dump_directories
|
5
|
+
@dump_directories ||= []
|
6
|
+
end
|
7
|
+
|
8
|
+
def restore_from_directories
|
9
|
+
@restore_from_directories ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
def dump(directory)
|
13
|
+
dump_directories << directory
|
14
|
+
end
|
15
|
+
|
16
|
+
def restore_from(directory)
|
17
|
+
restore_from_directories << directory
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
%w(mock_a mock_b mock_c).each do |mock_name|
|
22
|
+
store = Class.new(RecordingStore)
|
23
|
+
store.class_eval <<-DEF
|
24
|
+
def name
|
25
|
+
#{mock_name.inspect}
|
26
|
+
end
|
27
|
+
DEF
|
28
|
+
Leda::Store.register_store(store, mock_name)
|
29
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Leda::Spec
|
4
|
+
module Tmpdir
|
5
|
+
def spec_tmpdir(path=nil)
|
6
|
+
@spec_tmpdir ||= Pathname.new(File.expand_path("../../spec/tmp", __FILE__))
|
7
|
+
full = path ? @spec_tmpdir.join(path) : @spec_tmpdir
|
8
|
+
full.mkpath
|
9
|
+
full
|
10
|
+
end
|
11
|
+
|
12
|
+
def rm_tmpdir
|
13
|
+
if @spec_tempdir
|
14
|
+
@spec_tempdir.rmtree
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|