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.
@@ -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