Skip to content

Instantly share code, notes, and snippets.

@urfolomeus
Created August 4, 2012 15:12
Show Gist options
  • Save urfolomeus/3258292 to your computer and use it in GitHub Desktop.
Save urfolomeus/3258292 to your computer and use it in GitHub Desktop.
Quick script to migrate heroku apps to the new free individual postgres DBs
## README
# This is a quick script I hacked out to migrate all my heroku apps
# to the new free individual postgres DBs. To use it:
# - install the heroku gem if you don't already have it
# - set the value of IGNORE_OTHERS_APPS to true if you only want to
# run the script against apps you've created yourself
# - add any apps you want to ignore because they don't use PostgreSQL
# (or for any other reason) to the IGNORE_LIST
## CAVEAT!!
# - USE AT YOUR OWN RISK!! This works for me, but it may not work for you!
# - I don't remove the SHARED_DATABASE at the end of the process in
# case you want to rollback, so you'll need to delete it yourself once
# you're happy everything worked
# heroku addons:remove shared-database
# - if everything goes south you can just try again, but you'll have to make
# sure that you set the shared database back to be primary by running
# heroku pg:promote SHARED_DATABASE --app <app_name>
## N.B.
# If you do this before Aug 9th then you get:
# - an extra 4000 lines for your DB if on the dev plan
# - $20 credit if on basic or production plan
#
# More info at: https://devcenter.heroku.com/articles/migrating-from-shared-database-to-heroku-postgres
## OPTIONS
IGNORE_OTHERS_APPS = true
IGNORE_LIST = [ ]
class HerokuDBMigrator
def initialize(app_name)
@app_name = app_name
end
def run
raise "No app_name set" if @app_name.nil? or @app_name == ""
maintenance :on
add_required_addons
transfer
rescue Exception => e
p e
ensure
maintenance :off
end
private
def maintenance(state)
heroku "maintenance:#{state}"
puts "maintenance #{state}"
end
def add_required_addons
add "heroku-postgresql:dev"
add "pgbackups"
end
def add(addon)
unless installed?(addon)
puts "adding #{addon}"
heroku "addons:add #{addon}"
end
end
def get_addons
heroku('addons').split(/\n/).map{|a| a.split(' => ').first}.compact
end
def installed?(addon)
@addons ||= get_addons
not @addons.select {|a| a.include?(addon)}.empty?
end
def transfer
backup_existing_db
setup_new_db
end
def backup_existing_db
puts "backing up existing DB"
heroku "pgbackups:capture --expire"
end
def setup_new_db
db_name = heroku("config").match(/(HEROKU_POSTGRESQL_.*)_URL/)[1]
puts "setting up #{db_name}"
heroku "pgbackups:restore #{db_name} --confirm #{@app_name}"
puts "making #{db_name} primary"
heroku "pg:promote #{db_name}"
end
def heroku(command)
`heroku #{command} --app #{@app_name}`
end
end
apps = `heroku apps`.split(/\n/)
apps.reject! {|app| app =~ /\s+/} if IGNORE_OTHERS_APPS
apps -= IGNORE_LIST
apps.each do |app|
puts "\nTransferring app: #{app}"
HerokuDBMigrator.new(app).run
puts
end
@urfolomeus
Copy link
Author

Yeah I tested that one as it seemed most obvious culprit (although I'd have thought that no add-ons in heroku would just return an empty array which means the #include? in the select would never get called). I thought that what was happening was that the second split on ' => ' was returning an array with nil as the first element, but I couldn't get any output for 'heroku addons' to produce that result. I figured that by compacting the array (not uniq as I first did in my hungover state :D) would remove any nils and therefore remove errors so I just went with that.

What _was_quite interesting though (for a given vaue of interesting) was that the output you pasted showed that no app_name was being passed in to the HerokuDBMigrator. That would cause all calls to be made with --app at the end with no app. So I reckon I must be incorrectly parsing the error message returned from that. So if you can figure out how it managed to get a nil/blank app name, you'll be on the right path ;)

@urfolomeus
Copy link
Author

Turns out that's exactly what it was. If there you do heroku addons --app you get the usage message returned, which in turn generates an array with nils in it. I've left the compact in, but I've also changed the run method to raise an error before starting if no app_name is set.

Thanks for the heads up :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment