leda 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|