class Heroku::Command::Fork
clone an existing app
Public Instance Methods
index()
click to toggle source
fork [NEWNAME]
Fork
an existing app – copy config vars and Heroku
Postgres data, and re-provision add-ons to a new app. New app name should not be an existing app. The new app will be created as part of the forking process.
-s, –stack STACK # specify a stack for the new app –region REGION # specify a region
# File lib/heroku/command/fork.rb, line 18 def index from = app to = shift_argument || "#{from}-#{(rand*1000).to_i}" from_info = api.get_app(from).body to_info = action("Creating fork #{to}") do api.post_app({ :name => to, :region => options[:region] || from_info["region"], :stack => options[:stack] || from_info["stack"], :tier => from_info["tier"] == "legacy" ? "production" : from_info["tier"] }).body end action("Copying slug") do job_location = cisaurus_client.copy_slug(from, to) loop do break if cisaurus_client.job_done?(job_location) sleep 1 end end from_config = api.get_config_vars(from).body from_addons = api.get_addons(from).body from_addons.each do |addon| print "Adding #{addon["name"]}... " begin to_addon = api.post_addon(to, addon["name"]).body puts "done" rescue Heroku::API::Errors::RequestFailed => ex puts "skipped (%s)" % json_decode(ex.response.body)["error"] rescue Heroku::API::Errors::NotFound puts "skipped (not found)" end if addon["name"] =~ /^heroku-postgresql:/ from_var_name = "#{addon["attachment_name"]}_URL" from_attachment = to_addon["message"].match(/Attached as (\w+)_URL\n/)[1] if from_config[from_var_name] == from_config["DATABASE_URL"] from_config["DATABASE_URL"] = api.get_config_vars(to).body["#{from_attachment}_URL"] end from_config.delete(from_var_name) plan = addon["name"].split(":").last unless %w(dev basic).include? plan wait_for_db to, to_addon end check_for_pgbackups! from check_for_pgbackups! to migrate_db addon, from, to_addon, to end end to_config = api.get_config_vars(to).body action("Copying config vars") do diff = from_config.inject({}) do |ax, (key, val)| ax[key] = val unless to_config[key] ax end api.put_config_vars to, diff end puts "Fork complete, view it at #{to_info['web_url']}" end
Private Instance Methods
check_for_pgbackups!(app)
click to toggle source
# File lib/heroku/command/fork.rb, line 94 def check_for_pgbackups!(app) unless api.get_addons(app).body.detect { |addon| addon["name"] =~ /^pgbackups:/ } action("Adding pgbackups:plus to #{app}") do api.post_addon app, "pgbackups:plus" end end end
cisaurus_client()
click to toggle source
# File lib/heroku/command/fork.rb, line 89 def cisaurus_client cisaurus_url = ENV["CISAURUS_HOST"] || "https://cisaurus.herokuapp.com" @cisaurus_client ||= Heroku::Client::Cisaurus.new(cisaurus_url) end
migrate_db(from_addon, from, to_addon, to)
click to toggle source
# File lib/heroku/command/fork.rb, line 102 def migrate_db(from_addon, from, to_addon, to) transfer = nil action("Creating database backup from #{from} (this can take some time)") do from_config = api.get_config_vars(from).body from_attachment = from_addon["attachment_name"] pgb = Heroku::Client::Pgbackups.new(from_config["PGBACKUPS_URL"]) transfer = pgb.create_transfer(from_config["#{from_attachment}_URL"], from_attachment, nil, "BACKUP", :expire => "true") error transfer["errors"].values.flatten.join("\n") if transfer["errors"] loop do transfer = pgb.get_transfer(transfer["id"]) error transfer["errors"].values.flatten.join("\n") if transfer["errors"] break if transfer["finished_at"] sleep 1 end print " " end action("Restoring database backup to #{to} (this can take some time)") do to_config = api.get_config_vars(to).body to_attachment = to_addon["message"].match(/Attached as (\w+)_URL\n/)[1] pgb = Heroku::Client::Pgbackups.new(to_config["PGBACKUPS_URL"]) transfer = pgb.create_transfer(transfer["public_url"], "EXTERNAL_BACKUP", to_config["#{to_attachment}_URL"], to_attachment) error transfer["errors"].values.flatten.join("\n") if transfer["errors"] loop do transfer = pgb.get_transfer(transfer["id"]) error transfer["errors"].values.flatten.join("\n") if transfer["errors"] break if transfer["finished_at"] sleep 1 end print " " end end
pg_api(starter=false)
click to toggle source
# File lib/heroku/command/fork.rb, line 136 def pg_api(starter=false) host = starter ? "postgres-starter-api.heroku.com" : "postgres-api.heroku.com" RestClient::Resource.new "https://#{host}/client/v11/databases", Heroku::Auth.user, Heroku::Auth.password end
wait_for_db(app, attachment)
click to toggle source
# File lib/heroku/command/fork.rb, line 141 def wait_for_db(app, attachment) attachments = api.get_attachments(app).body.inject({}) { |ax,att| ax.update(att["name"] => att["resource"]["name"]) } attachment_name = attachment["message"].match(/Attached as (\w+)_URL\n/)[1] action("Waiting for database to be ready (this can take some time)") do loop do begin waiting = json_decode(pg_api["#{attachments[attachment_name]}/wait_status"].get.to_s)["waiting?"] break unless waiting sleep 5 rescue RestClient::ResourceNotFound rescue Interrupt exit 0 end end print " " end end