prodder 1.7
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 +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
|