class HerokuRailsDeploy::Deployer

Constants

PRODUCTION
PRODUCTION_BRANCH_REGEX

Attributes

args[R]
config_file[R]

Public Class Methods

new(config_file, args) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 25
def initialize(config_file, args)
  raise "Missing config file #{config_file}" unless File.file?(config_file)
  @app_registry = YAML.load(File.read(config_file))
  @config_file = config_file
  @args = args
  @options = parse_options
end

Public Instance Methods

production?() click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 72
def production?
  options.try(:environment) == PRODUCTION
end
run() click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 33
def run
  return unless options

  app_name = app_registry.fetch(options.environment) do
    raise OptionParser::InvalidArgument.new("Invalid environment '#{options.environment}'. " \
      "Must be in #{app_registry.keys.join(', ')}")
  end

  raise 'Only master, release or hotfix branches can be deployed to production' if production? && !production_branch?(options.revision)

  no_uncommitted_changes!

  puts "Deploying to Heroku app #{app_name} for environment #{options.environment}"

  if !options.skip_avro_schemas && (production? || options.register_avro_schemas)
    puts 'Checking for pending Avro schemas'
    pending_schemas = list_pending_schemas(app_name)
    if pending_schemas.any?
      puts 'Registering Avro schemas'
      register_avro_schemas!(registry_url(app_name), pending_schemas)
    else
      puts 'No pending Avro schemas'
    end
  end

  puts 'Pushing code'
  push_code(app_name, options.revision)

  puts 'Checking for pending migrations'
  if pending_migrations?(app_name)
    puts 'Running migrations'
    run_migrations(app_name)
    puts 'Restarting dynos'
    restart_dynos(app_name)
  else
    puts 'No migrations required'
  end
end

Private Instance Methods

app_remote(app_name) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 188
def app_remote(app_name)
  "https://git.heroku.com/#{app_name}.git"
end
changed_files(app_name) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 176
def changed_files(app_name)
  run_command("git diff --name-only #{remote_commit(app_name)}..#{current_commit}", quiet: true).split("\n").map(&:strip)
end
current_commit() click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 180
def current_commit
  run_command!("git log --pretty=format:'%H' -n 1", quiet: true)
end
git_branch_name(revision) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 125
def git_branch_name(revision)
  run_command!("git rev-parse --abbrev-ref #{Shellwords.escape(revision)}", quiet: true).strip
rescue
  raise "Unable to get branch for #{revision}"
end
list_pending_schemas(app_name) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 172
def list_pending_schemas(app_name)
  changed_files(app_name).select { |filename| /\.avsc$/ =~ filename }
end
no_uncommitted_changes!() click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 116
def no_uncommitted_changes!
  uncommitted_changes = run_command!('git status --porcelain', quiet: true)
  raise "There are uncommitted changes:\n#{uncommitted_changes}" unless uncommitted_changes.blank?
end
parse_options() click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 78
def parse_options
  options = Options.create_default(app_registry)
  OptionParser.new do |parser|
    parser.on_tail('-h', '--help', 'Show this message') do
      puts parser
      # rubocop:disable Lint/NonLocalExitFromIterator
      return
      # rubocop:enable Lint/NonLocalExitFromIterator
    end

    parser.on('-e', '--environment ENVIRONMENT',
              "The environment to deploy to. Must be in #{app_registry.keys.join(', ')} (default #{app_registry.keys.first})") do |environment|
      options.environment = environment
    end

    parser.on('-r', '--revision REVISION',
              'The git revision to push. (default HEAD)') do |revision|
      options.revision = revision
    end

    parser.on('--register-avro-schemas',
              'Force the registration of Avro schemas when deploying to a non-production environment.') do |register_avro_schemas|
      options.register_avro_schemas = register_avro_schemas
    end

    parser.on('--skip-avro-schemas',
              'Skip the registration of Avro schemas when deploying to production.') do |skip_avro_schemas|
      options.skip_avro_schemas = skip_avro_schemas
    end
  end.parse!(args)

  options
end
pending_migrations?(app_name) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 139
def pending_migrations?(app_name)
  !run_heroku_command(app_name, 'run rake db:abort_if_pending_migrations')
end
production_branch?(revision) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 112
def production_branch?(revision)
  git_branch_name(revision).match(PRODUCTION_BRANCH_REGEX)
end
push_code(app_name, revision) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 121
def push_code(app_name, revision)
  run_command!("git push --force #{app_remote(app_name)} #{revision}:master")
end
register_avro_schemas!(registry_url, schemas) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 167
def register_avro_schemas!(registry_url, schemas)
  cmd = "rake avro:register_schemas schemas=#{schemas.join(',')}"
  run_command!("DEPLOYMENT_SCHEMA_REGISTRY_URL=#{registry_url} #{cmd}", print_command: cmd)
end
registry_url(app_name) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 162
def registry_url(app_name)
  result = run_command!("heroku config -a #{app_name} | grep AVRO_SCHEMA_REGISTRY_URL:", quiet: true)
  result.split.last
end
remote_commit(app_name) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 184
def remote_commit(app_name)
  run_command!("git ls-remote --heads #{app_remote(app_name)} master", quiet: true).split.first
end
restart_dynos(app_name) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 135
def restart_dynos(app_name)
  run_heroku_command!(app_name, 'ps:restart')
end
run_command(command, print_command: nil, validate: nil, quiet: false, display_output: false) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 196
def run_command(command, print_command: nil, validate: nil, quiet: false, display_output: false)
  printed_command = print_command || command
  puts printed_command unless quiet
  if display_output
    Bundler.with_clean_env { system(command) }
  else
    output = Bundler.with_clean_env { `#{command}` }
  end
  success = $CHILD_STATUS.success?
  raise "Command '#{printed_command}' failed#{output && output.strip != '' ? ", output: #{output}" : ''}" if validate && !success
  display_output ? success : output
end
run_command!(command, print_command: nil, quiet: false, display_output: false) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 192
def run_command!(command, print_command: nil, quiet: false, display_output: false)
  run_command(command, print_command: print_command, quiet: quiet, display_output: display_output, validate: true)
end
run_heroku_command(app_name, command, validate: nil) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 149
def run_heroku_command(app_name, command, validate: nil)
  cli_command = "heroku #{command} --app #{app_name}"
  if command.start_with?('run ')
    # If we're running a shell command, return the underlying
    # shell command exit code
    cli_command << ' --exit-code'
    display_output = true
  else
    display_output = false
  end
  run_command(cli_command, validate: validate, display_output: display_output)
end
run_heroku_command!(app_name, command) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 143
def run_heroku_command!(app_name, command)
  run_heroku_command(app_name, command, validate: true)
rescue
  raise "Heroku command '#{command}' failed"
end
run_migrations(app_name) click to toggle source
# File lib/heroku_rails_deploy/deployer.rb, line 131
def run_migrations(app_name)
  run_heroku_command!(app_name, 'run rake db:migrate')
end