prodder 1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +52 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +64 -0
- data/LICENSE.txt +21 -0
- data/README.md +272 -0
- data/Rakefile +20 -0
- data/bin/prodder +4 -0
- data/features/commit.feature +69 -0
- data/features/dump.feature +187 -0
- data/features/init.feature +24 -0
- data/features/lint.feature +46 -0
- data/features/prodder.feature +76 -0
- data/features/push.feature +25 -0
- data/features/step_definitions/git_steps.rb +65 -0
- data/features/step_definitions/prodder_steps.rb +150 -0
- data/features/support/blog.git.tgz +0 -0
- data/features/support/env.rb +116 -0
- data/features/support/prodder__blog_prod.sql +153 -0
- data/lib/prodder.rb +5 -0
- data/lib/prodder/cli.rb +135 -0
- data/lib/prodder/config.rb +95 -0
- data/lib/prodder/git.rb +97 -0
- data/lib/prodder/pg.rb +486 -0
- data/lib/prodder/prodder.rake +390 -0
- data/lib/prodder/project.rb +150 -0
- data/lib/prodder/railtie.rb +7 -0
- data/lib/prodder/version.rb +3 -0
- data/prodder.gemspec +25 -0
- data/spec/config_spec.rb +64 -0
- data/spec/spec_helper.rb +3 -0
- metadata +91 -0
@@ -0,0 +1,390 @@
|
|
1
|
+
module Prodder
|
2
|
+
# The list of default rake tasks which prodder will be removing or replacing.
|
3
|
+
# @see databases.rake (currently at lib/active_record/railties/databases.rake)
|
4
|
+
def self.obsoleted_rake_tasks
|
5
|
+
[/^db:_dump$/,
|
6
|
+
/^db:migrate:reset$/,
|
7
|
+
/^db:drop$/,
|
8
|
+
/^db:create$/,
|
9
|
+
/^db:drop:all$/,
|
10
|
+
/^db:create:all$/,
|
11
|
+
/^db:migrate$/,
|
12
|
+
/^db:migrate:up$/,
|
13
|
+
/^db:migrate:down$/,
|
14
|
+
/^db:rollback$/,
|
15
|
+
/^db:forward$/,
|
16
|
+
/^db:version$/,
|
17
|
+
/^db:fixtures:.*$/,
|
18
|
+
/^db:abort_if_pending_migrations$/,
|
19
|
+
/^db:purge$/,
|
20
|
+
/^db:purge:all$/,
|
21
|
+
/^db:charset$/,
|
22
|
+
/^db:collation$/,
|
23
|
+
/^db:reset$/,
|
24
|
+
/^db:schema:.*$/,
|
25
|
+
/^db:seed$/,
|
26
|
+
/^db:setup$/,
|
27
|
+
/^db:structure:.*$/,
|
28
|
+
/^db:test:.*$/,
|
29
|
+
/^test:prepare$/
|
30
|
+
]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
tasks = Rake.application.instance_variable_get :@tasks
|
35
|
+
tasks.keys.select { |name|
|
36
|
+
Prodder.obsoleted_rake_tasks.any? { |obsoleted| obsoleted.match(name) }
|
37
|
+
}.each { |name| tasks.delete name }
|
38
|
+
|
39
|
+
namespace :db do
|
40
|
+
desc "Drop, recreate, reseed, remigrate the database"
|
41
|
+
task :reset => ['db:drop', 'db:setup']
|
42
|
+
|
43
|
+
desc "Create the database, load db/structure.sql, db/seeds.sql, db/quality_checks.sql"
|
44
|
+
task :setup => ['db:create', 'db:structure:load', 'db:seed', 'db:quality_check', 'db:permission', 'db:settings']
|
45
|
+
|
46
|
+
dependencies = [:load_config]
|
47
|
+
if Rake::Task.task_defined?('rails_env')
|
48
|
+
dependencies << :rails_env
|
49
|
+
end
|
50
|
+
|
51
|
+
namespace :migrate do
|
52
|
+
task :up => [:environment].concat(dependencies) do
|
53
|
+
version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
|
54
|
+
raise 'VERSION is required' unless version
|
55
|
+
as("migration_user", in: ENV['RAILS_ENV'] || Rails.env) do
|
56
|
+
ActiveRecord::Base.establish_connection((ENV['RAILS_ENV'] || Rails.env).intern)
|
57
|
+
ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
task :down => [:environment].concat(dependencies) do
|
62
|
+
version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
|
63
|
+
raise 'VERSION is required - To go down one migration, run db:rollback' unless version
|
64
|
+
as("migration_user", in: ENV['RAILS_ENV'] || Rails.env) do
|
65
|
+
ActiveRecord::Base.establish_connection((ENV['RAILS_ENV'] || Rails.env).intern)
|
66
|
+
ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
namespace :purge do
|
72
|
+
task :all => dependencies do
|
73
|
+
as("superuser") do
|
74
|
+
ActiveRecord::Tasks::DatabaseTasks.purge_all
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
|
80
|
+
task :purge => dependencies do
|
81
|
+
as("superuser", in: ENV['RAILS_ENV'] || [Rails.env, "test"]) do
|
82
|
+
ActiveRecord::Tasks::DatabaseTasks.purge_current
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
desc "Retrieves the charset for the current environment's database"
|
87
|
+
task :charset => [:environment].concat(dependencies) do
|
88
|
+
as("migration_user", in: ENV['RAILS_ENV'] || Rails.env) do
|
89
|
+
puts ActiveRecord::Tasks::DatabaseTasks.charset_current
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "Retrieves the collation for the current environment's database"
|
94
|
+
task :collation => [:environment].concat(dependencies) do
|
95
|
+
as("migration_user", in: ENV['RAILS_ENV'] || Rails.env) do
|
96
|
+
begin
|
97
|
+
puts ActiveRecord::Tasks::DatabaseTasks.collation_current
|
98
|
+
rescue NoMethodError
|
99
|
+
$stderr.puts 'Sorry, your database adapter is not supported yet. Feel free to submit a patch.'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).'
|
105
|
+
task :rollback => [:environment].concat(dependencies) do
|
106
|
+
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
107
|
+
as("migration_user", in: ENV['RAILS_ENV'] || Rails.env) do
|
108
|
+
ActiveRecord::Base.establish_connection((ENV['RAILS_ENV'] || Rails.env).intern)
|
109
|
+
ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
desc 'Pushes the schema to the next version (specify steps w/ STEP=n).'
|
114
|
+
task :forward => [:environment].concat(dependencies) do
|
115
|
+
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
116
|
+
as("migration_user", in: ENV['RAILS_ENV'] || Rails.env) do
|
117
|
+
ActiveRecord::Base.establish_connection((ENV['RAILS_ENV'] || Rails.env).intern)
|
118
|
+
ActiveRecord::Migrator.forward(ActiveRecord::Migrator.migrations_paths, step)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
desc 'Retrieves the current schema version number'
|
123
|
+
task :version => [:environment].concat(dependencies) do
|
124
|
+
as("migration_user", in: ENV['RAILS_ENV'] || Rails.env) do
|
125
|
+
ActiveRecord::Base.establish_connection((ENV['RAILS_ENV'] || Rails.env).intern)
|
126
|
+
puts "Current version: #{ActiveRecord::Migrator.current_version}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
task :abort_if_pending_migrations => [:environment].concat(dependencies) do
|
131
|
+
as("migration_user", in: ENV['RAILS_ENV'] || Rails.env) do
|
132
|
+
ActiveRecord::Base.establish_connection((ENV['RAILS_ENV'] || Rails.env).intern)
|
133
|
+
pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations
|
134
|
+
|
135
|
+
if pending_migrations.any?
|
136
|
+
puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
|
137
|
+
pending_migrations.each do |pending_migration|
|
138
|
+
puts ' %4d %s' % [pending_migration.version, pending_migration.name]
|
139
|
+
end
|
140
|
+
abort %{Run `rake db:migrate` to update your database then try again.}
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
namespace :drop do
|
146
|
+
task :all => dependencies do
|
147
|
+
as("superuser") do
|
148
|
+
ActiveRecord::Tasks::DatabaseTasks.drop_all
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
desc "Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV, it defaults to dropping the development and test databases."
|
154
|
+
task :drop => dependencies do
|
155
|
+
as("superuser", in: ENV['RAILS_ENV'] || [Rails.env, "test"]) do
|
156
|
+
ActiveRecord::Tasks::DatabaseTasks.drop_current
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV, it defaults to creating the development and test databases.'
|
161
|
+
task :create => dependencies do
|
162
|
+
environments = nil
|
163
|
+
if ENV['RAILS_ENV']
|
164
|
+
environments = Array(ENV['RAILS_ENV'])
|
165
|
+
else
|
166
|
+
environments = [Rails.env, "test"]
|
167
|
+
end
|
168
|
+
as("superuser", in: environments) do
|
169
|
+
ActiveRecord::Tasks::DatabaseTasks.create_current
|
170
|
+
ActiveRecord::Base.configurations.each do |env, config|
|
171
|
+
if environments.include?(env) && config["migration_user"] && config['database']
|
172
|
+
`psql --no-psqlrc --command "ALTER DATABASE #{config['database']} OWNER TO #{config['migration_user']}" #{Shellwords.escape(config['database'])}`
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
namespace :create do
|
179
|
+
task :all => dependencies do
|
180
|
+
as("superuser") do
|
181
|
+
ActiveRecord::Tasks::DatabaseTasks.create_all
|
182
|
+
ActiveRecord::Base.configurations.each do |env, config|
|
183
|
+
if config["migration_user"] && config['database']
|
184
|
+
`psql --no-psqlrc --command "ALTER DATABASE #{config['database']} OWNER TO #{config['migration_user']}" #{Shellwords.escape(config['database'])}`
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
|
192
|
+
task :migrate => [:environment].concat(dependencies) do
|
193
|
+
as("migration_user", in: ENV['RAILS_ENV'] || Rails.env) do
|
194
|
+
ActiveRecord::Base.establish_connection((ENV['RAILS_ENV'] || Rails.env).intern)
|
195
|
+
ActiveRecord::Tasks::DatabaseTasks.migrate
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
namespace :structure do
|
200
|
+
desc "Load db/structure.sql into the current environment's database"
|
201
|
+
task :load => dependencies do
|
202
|
+
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env]
|
203
|
+
config["username"] = config["superuser"] if config["superuser"] && File.exist?('db/permissions.sql')
|
204
|
+
set_psql_env config
|
205
|
+
puts "Loading db/structure.sql into database '#{config['database']}'"
|
206
|
+
`psql --no-psqlrc -f db/structure.sql #{Shellwords.escape(config['database'])}`
|
207
|
+
raise 'Error loading db/structure.sql' if $?.exitstatus != 0
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
desc "Load initial seeds from db/seeds.sql"
|
212
|
+
task :seed => dependencies do
|
213
|
+
if File.exist?('db/seeds.sql')
|
214
|
+
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env]
|
215
|
+
config["username"] = config["superuser"] if config["superuser"] && File.exist?('db/permissions.sql')
|
216
|
+
set_psql_env config
|
217
|
+
puts "Loading db/seeds.sql into database '#{config['database']}'"
|
218
|
+
`psql --no-psqlrc -f db/seeds.sql #{Shellwords.escape(config['database'])}`
|
219
|
+
raise 'Error loading db/seeds.sql' if $?.exitstatus != 0
|
220
|
+
else
|
221
|
+
puts 'db/seeds.sql not found: no seeds to load.'
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
desc "Load quality_checks (indexes, triggers, foreign keys) from db/quality_checks.sql"
|
226
|
+
task :quality_check => dependencies do
|
227
|
+
if File.exist?('db/quality_checks.sql')
|
228
|
+
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env]
|
229
|
+
config["username"] = config["superuser"] if config["superuser"] && File.exist?('db/permissions.sql')
|
230
|
+
set_psql_env config
|
231
|
+
puts "Loading db/quality_checks.sql into database '#{config['database']}'"
|
232
|
+
`psql --no-psqlrc -f db/quality_checks.sql #{Shellwords.escape(config['database'])}`
|
233
|
+
raise 'Error loading db/quality_checks.sql' if $?.exitstatus != 0
|
234
|
+
else
|
235
|
+
puts 'db/quality_checks.sql not found: no quality_checks to load.'
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
desc "Load permissions (DB object level access control, group role memberships) from db/permissions.sql"
|
240
|
+
task :permission => dependencies do
|
241
|
+
if File.exist?('db/permissions.sql')
|
242
|
+
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env]
|
243
|
+
config["username"] = config["superuser"] if config["superuser"]
|
244
|
+
set_psql_env config
|
245
|
+
puts "Loading db/permissions.sql into database '#{config['database']}'"
|
246
|
+
disconnect
|
247
|
+
ActiveRecord::Base.establish_connection((ENV['RAILS_ENV'] || Rails.env).intern)
|
248
|
+
is_super = ActiveRecord::Base.connection.execute(<<-SQL).first['is_super']
|
249
|
+
select 1 as is_super from pg_roles where rolname = '#{config['username']}' and rolsuper
|
250
|
+
SQL
|
251
|
+
unless is_super
|
252
|
+
puts "Restoring permissions as config/database.yml non-superuser: #{config['username']}, expect errors, or rerun after granting superuser"
|
253
|
+
end
|
254
|
+
`psql --no-psqlrc -f db/permissions.sql #{Shellwords.escape(config['database'])}`
|
255
|
+
|
256
|
+
raise 'Error loading db/permissions.sql' if $?.exitstatus != 0
|
257
|
+
else
|
258
|
+
puts 'db/permissions.sql not found: no permissions to load.'
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
desc "Load database settings"
|
263
|
+
task :settings => dependencies do
|
264
|
+
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env]
|
265
|
+
config["username"] = config["superuser"] if config["superuser"] && File.exist?('db/permissions.sql')
|
266
|
+
set_psql_env config
|
267
|
+
puts "Loading db/settings.sql into database '#{config['database']}'"
|
268
|
+
disconnect
|
269
|
+
ActiveRecord::Base.establish_connection((ENV['RAILS_ENV'] || Rails.env).intern)
|
270
|
+
is_super = ActiveRecord::Base.connection.execute(<<-SQL).first['is_super']
|
271
|
+
select 1 as is_super from pg_roles where rolname = '#{config['username']}' and rolsuper
|
272
|
+
SQL
|
273
|
+
unless is_super
|
274
|
+
puts "Restoring settings as config/database.yml non-superuser: #{config['username']}, expect errors, or rerun after granting superuser"
|
275
|
+
end
|
276
|
+
`psql --no-psqlrc -f db/settings.sql #{Shellwords.escape(config['database'])}`
|
277
|
+
|
278
|
+
raise 'Error loading db/settings.sql' if $?.exitstatus != 0
|
279
|
+
end
|
280
|
+
|
281
|
+
# Empty this, we don't want db:migrate writing structure.sql any more.
|
282
|
+
task :_dump do
|
283
|
+
end
|
284
|
+
|
285
|
+
# Ugh. cucumber.rake, installed by the cucumber generator, always uses a task dependency
|
286
|
+
# on db:test:prepare. rspec.rake, contained within rspec-rails, uses either db:test:prepare
|
287
|
+
# or db:test:clone_structure, depending on what schema_format you declare.
|
288
|
+
#
|
289
|
+
# Gut and redefine both.
|
290
|
+
namespace :test do
|
291
|
+
task :prepare do
|
292
|
+
begin
|
293
|
+
orig_env_var, orig_rails_var = ENV['RAILS_ENV'], Rails.env
|
294
|
+
Rails.env = ENV['RAILS_ENV'] = 'test'
|
295
|
+
Rake::Task['db:reset'].invoke
|
296
|
+
Rake::Task['db:migrate'].invoke
|
297
|
+
ensure
|
298
|
+
ENV['RAILS_ENV'], Rails.env = orig_env_var, orig_rails_var
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# What rspec calls as a prereq to :spec
|
303
|
+
task :clone_structure => :prepare
|
304
|
+
end
|
305
|
+
|
306
|
+
# Exposed as a global method in Rails 3.x, but moved to a private method in Rails 4.
|
307
|
+
# We should instead be registering our own `seed_loader`, which would obviate a lot
|
308
|
+
# of this hackery to support Rails 4.
|
309
|
+
if !defined?(set_psql_env)
|
310
|
+
def set_psql_env(config)
|
311
|
+
ENV['PGHOST'] = config['host'] if config['host']
|
312
|
+
ENV['PGPORT'] = config['port'].to_s if config['port']
|
313
|
+
ENV['PGPASSWORD'] = config['password'].to_s if config['password']
|
314
|
+
ENV['PGUSER'] = config['username'].to_s if config['username']
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
#adding to the Rails hackery
|
319
|
+
if !defined?(ActiveRecord::Tasks::DatabaseTasks.migrate)
|
320
|
+
module ActiveRecord::Tasks::DatabaseTasks
|
321
|
+
def migrate
|
322
|
+
verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
|
323
|
+
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
|
324
|
+
scope = ENV['SCOPE']
|
325
|
+
verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, verbose
|
326
|
+
ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, version) do |migration|
|
327
|
+
scope.blank? || scope == migration.scope
|
328
|
+
end
|
329
|
+
ensure
|
330
|
+
ActiveRecord::Migration.verbose = verbose_was
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
if !defined?(ActiveRecord::Tasks::DatabaseTasks.purge_all)
|
336
|
+
module ActiveRecord::Tasks::DatabaseTasks
|
337
|
+
def purge_all
|
338
|
+
each_local_configuration { |configuration|
|
339
|
+
purge configuration
|
340
|
+
}
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
if !defined?(ActiveRecord::Tasks::DatabaseTasks.purge_current)
|
346
|
+
module ActiveRecord::Tasks::DatabaseTasks
|
347
|
+
def purge_current(environment = env)
|
348
|
+
each_current_configuration(environment) { |configuration|
|
349
|
+
purge configuration
|
350
|
+
}
|
351
|
+
ActiveRecord::Base.establish_connection(environment.to_sym)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def as(user, opts = {}, &block)
|
357
|
+
if File.exist?('db/permissions.sql')
|
358
|
+
config, config_was = ActiveRecord::Base.configurations.deep_dup, ActiveRecord::Base.configurations.deep_dup
|
359
|
+
in_env = Array(opts[:in]) || config.keys
|
360
|
+
if config.all? { |env, config_hash| in_env.include?(env) ? config_hash[user] : true }
|
361
|
+
disconnect
|
362
|
+
config.each { |env, config_hash| config_hash["username"] = config_hash[user] if in_env.include?(env) }
|
363
|
+
ActiveRecord::Base.configurations = config
|
364
|
+
end
|
365
|
+
else
|
366
|
+
puts "No permissions file (db/permissions.sql) found, running everything in context of user"
|
367
|
+
end
|
368
|
+
yield
|
369
|
+
ensure
|
370
|
+
ActiveRecord::Base.configurations = config_was if config_was
|
371
|
+
in_env.each { |env| ActiveRecord::Base.establish_connection(env.intern) } if in_env
|
372
|
+
end
|
373
|
+
|
374
|
+
def disconnect
|
375
|
+
if ActiveRecord::Base.connection_pool && ActiveRecord::Base.connection_pool.connections.size > 0
|
376
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
377
|
+
end
|
378
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
379
|
+
end
|
380
|
+
|
381
|
+
end
|
382
|
+
|
383
|
+
namespace :test do
|
384
|
+
task :prepare => [ 'db:test:prepare' ]
|
385
|
+
end
|
386
|
+
|
387
|
+
# Yes, I really want migrations to run against the test DB.
|
388
|
+
Rake::Task['db:migrate'].actions.unshift(proc {
|
389
|
+
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env])
|
390
|
+
})
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'deject'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'prodder/pg'
|
4
|
+
require 'prodder/git'
|
5
|
+
|
6
|
+
module Prodder
|
7
|
+
class Project
|
8
|
+
SeedConfigFileMissing = Class.new(StandardError) do
|
9
|
+
attr_reader :filename
|
10
|
+
def initialize(filename); @filename = filename; end
|
11
|
+
end
|
12
|
+
|
13
|
+
Deject self
|
14
|
+
dependency(:pg) { |project| Prodder::PG.new(project.db_credentials) }
|
15
|
+
dependency(:git) { |project| Prodder::Git.new(project.local_git_path, project.git_origin) }
|
16
|
+
|
17
|
+
attr_reader :name, :workspace
|
18
|
+
|
19
|
+
def initialize(name, workspace, definition)
|
20
|
+
@name = name
|
21
|
+
@workspace = workspace
|
22
|
+
@defn = definition
|
23
|
+
end
|
24
|
+
|
25
|
+
def init
|
26
|
+
git.clone_or_remote_update
|
27
|
+
end
|
28
|
+
|
29
|
+
def dump
|
30
|
+
FileUtils.mkdir_p File.dirname(structure_file_name)
|
31
|
+
pg.dump_structure db_credentials['name'], structure_file_name,
|
32
|
+
exclude_tables: excluded_tables, exclude_schemas: excluded_schemas
|
33
|
+
|
34
|
+
FileUtils.mkdir_p File.dirname(settings_file_name)
|
35
|
+
pg.dump_settings db_credentials['name'], settings_file_name
|
36
|
+
|
37
|
+
FileUtils.mkdir_p File.dirname(seed_file_name)
|
38
|
+
pg.dump_tables db_credentials['name'], seed_tables, seed_file_name
|
39
|
+
|
40
|
+
# must split the structure file to allow data to be loaded after tables
|
41
|
+
# being created but before triggers and foreign keys are created. this
|
42
|
+
# facilitates validation during loading, yet avoids extra overhead and
|
43
|
+
# false errors
|
44
|
+
if separate_quality_checks?
|
45
|
+
contents = File.readlines(structure_file_name)
|
46
|
+
rgx = /^\-\- .* Type: INDEX; |^\-\- .* Type: TRIGGER; |^\-\- .* Type: FK CONSTRAINT; /
|
47
|
+
structure, *quality = contents.slice_before(rgx).to_a
|
48
|
+
quality_checks = structure.grep(/SET search_path/).last + quality.join
|
49
|
+
|
50
|
+
File.open(quality_check_file_name, 'w') { |f| f.write(quality_checks) }
|
51
|
+
File.open(structure_file_name, 'w') { |f| f.write(structure.join) }
|
52
|
+
end
|
53
|
+
|
54
|
+
if dump_permissions?
|
55
|
+
FileUtils.mkdir_p File.dirname(permissions_file_name)
|
56
|
+
pg.dump_permissions db_credentials['name'], permissions_file_name, included_users: included_users,
|
57
|
+
exclude_tables: excluded_tables, exclude_schemas: excluded_schemas
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def commit
|
63
|
+
return unless git.dirty?
|
64
|
+
git.add structure_file_name
|
65
|
+
git.add seed_file_name
|
66
|
+
git.add quality_check_file_name if separate_quality_checks?
|
67
|
+
git.add permissions_file_name if dump_permissions?
|
68
|
+
git.add settings_file_name
|
69
|
+
git.commit "Auto-commit by prodder", @defn['git']['author']
|
70
|
+
end
|
71
|
+
|
72
|
+
def push
|
73
|
+
if git.fast_forward?
|
74
|
+
git.push
|
75
|
+
else
|
76
|
+
raise Prodder::Git::NotFastForward.new(git_origin)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def nothing_to_push?
|
81
|
+
git.remote_update
|
82
|
+
git.no_new_commits?
|
83
|
+
end
|
84
|
+
|
85
|
+
def db_credentials
|
86
|
+
@defn['db']
|
87
|
+
end
|
88
|
+
|
89
|
+
def permissions
|
90
|
+
@defn['permissions']
|
91
|
+
end
|
92
|
+
|
93
|
+
def local_git_path
|
94
|
+
workspace
|
95
|
+
end
|
96
|
+
|
97
|
+
def git_origin
|
98
|
+
@defn['git']['origin']
|
99
|
+
end
|
100
|
+
|
101
|
+
def structure_file_name
|
102
|
+
File.join workspace, @defn['structure_file']
|
103
|
+
end
|
104
|
+
|
105
|
+
def settings_file_name
|
106
|
+
File.join workspace, 'db/settings.sql'
|
107
|
+
end
|
108
|
+
|
109
|
+
def seed_file_name
|
110
|
+
File.join workspace, @defn['seed_file']
|
111
|
+
end
|
112
|
+
|
113
|
+
def quality_check_file_name
|
114
|
+
File.join workspace, @defn['quality_check_file']
|
115
|
+
end
|
116
|
+
|
117
|
+
def permissions_file_name
|
118
|
+
File.join workspace, permissions['file']
|
119
|
+
end
|
120
|
+
|
121
|
+
def separate_quality_checks?
|
122
|
+
@defn.key? 'quality_check_file'
|
123
|
+
end
|
124
|
+
|
125
|
+
def dump_permissions?
|
126
|
+
@defn.key?('permissions') && permissions.key?('file')
|
127
|
+
end
|
128
|
+
|
129
|
+
def excluded_schemas
|
130
|
+
db_credentials['exclude_schemas'] || []
|
131
|
+
end
|
132
|
+
|
133
|
+
def excluded_tables
|
134
|
+
db_credentials['exclude_tables'] || []
|
135
|
+
end
|
136
|
+
|
137
|
+
def included_users
|
138
|
+
permissions['included_users'] || []
|
139
|
+
end
|
140
|
+
|
141
|
+
def seed_tables
|
142
|
+
value = db_credentials['tables']
|
143
|
+
return value unless value.is_a?(String)
|
144
|
+
|
145
|
+
path = File.join(workspace, value)
|
146
|
+
raise SeedConfigFileMissing.new(File.join(name, value)) unless File.exist?(path)
|
147
|
+
YAML.load IO.read(path)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|