class EY::Serverside::DeployBase

Public Instance Methods

abort_on_bad_paths_in_release_directory() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 214
def abort_on_bad_paths_in_release_directory
  shell.substatus "Checking for disruptive files in #{paths.releases}"

  bad_paths = paths.all_releases.reject do |path|
    path.basename.to_s =~ /^[\d]+$/
  end

  if bad_paths.any?
    shell.fatal "Bad paths found in #{paths.releases}:\n\t#{bad_paths.join("\n\t")}\nStoring files in this directory will disrupt latest_release, diff detection, rollback, and possibly other features."
    raise
  end
end
bundle() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 96
def bundle
  install_dependencies
end
cached_deploy() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 23
def cached_deploy
  shell.status "Deploying app from cached copy at #{Time.now.asctime}"
  load_ey_yml
  require_custom_tasks
  push_code

  shell.status "Starting full deploy"

  new_release do
    callback(:before_deploy)
    check_repository
    create_revision_file
    run_with_callbacks(:bundle)
    setup_services
    configure_platform
    symlink_configs
    setup_sqlite3_if_necessary
    run_with_callbacks(:compile_assets)
    enable_maintenance_page
    run_with_callbacks(:migrate)
    callback(:before_symlink)
    # We don't use run_with_callbacks for symlink because we need
    # to clean up manually if it fails.
    symlink
  end

  callback(:after_symlink)
  run_with_callbacks(:restart)
  disable_maintenance_page

  cleanup_old_releases
  gc_repository_cache
  callback(:after_deploy)
  shell.status "Finished deploy at #{Time.now.asctime}"
rescue Exception => e
  shell.status "Finished failing to deploy at #{Time.now.asctime}"
  shell.error "Exception during deploy: #{e.inspect}\n#{e.backtrace}"
  puts_deploy_failure
  raise
end
callback(what) click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 454
def callback(what)
  @callbacks_reached ||= true
  Callbacks::Distributor.distribute(self, what)
end
check_dependencies() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 88
def check_dependencies
  dependency_manager.check
end
check_repository() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 84
def check_repository
  check_dependencies
end
clean_release_directory(dir, count) click to toggle source

Remove all but the most-recent count releases from the specified release directory. IMPORTANT: This expects the release directory naming convention to be something with a sensible lexical order. Violate that at your peril.

# File lib/engineyard-serverside/deploy.rb, line 206
def clean_release_directory(dir, count)
  @cleanup_failed = true
  ordinal = count.succ.to_s
  shell.status "Cleaning release directory: #{dir}"
  sudo "ls -r #{dir} | tail -n +#{ordinal} | xargs -I@ rm -rf #{dir}/@"
  @cleanup_failed = false
end
cleanup_old_releases() click to toggle source

task

# File lib/engineyard-serverside/deploy.rb, line 192
def cleanup_old_releases
  count = config.keep_releases.to_i
  if count < 1
    shell.error "Refusing to delete all releases! keep_releases must be >= 1 (got #{count})"
    count = EY::Serverside::Deploy::Configuration::DEFAULT_KEEP_RELEASES
    shell.warning "Running instead with default value for keep_releases: #{count}"
  end
  clean_release_directory(paths.releases, count)
end
configure_command() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 351
      def configure_command
        ENV['EY_SERVERSIDE_CONFIGURE_COMMAND'] || begin
          configure_script = "/engineyard/bin/app_#{config.app}_configure"
          return <<-CONFIGURE
if [ -x "#{configure_script}" ] || which "#{configure_script}" >/dev/null 2>&1; then
  EY_DEPLOY_APP='#{config.app}' EY_DEPLOY_RELEASE_PATH='#{paths.active_release.to_s}' #{configure_script};
fi
          CONFIGURE
        end
      end
configure_platform() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 347
def configure_platform
  run configure_command
end
copy_repository_cache() click to toggle source

task

# File lib/engineyard-serverside/deploy.rb, line 277
def copy_repository_cache
  paths.new_release!
  shell.status "Copying to #{paths.active_release}"
  exclusions = Array(config.copy_exclude).map { |e| %|--exclude="#{e}"| }.join(' ')
  run("mkdir -p #{paths.active_release} #{paths.shared_config} && rsync -aq #{exclusions} #{paths.repository_cache}/ #{paths.active_release}")
end
create_revision_file() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 284
def create_revision_file
  run create_revision_file_command
end
create_revision_file_command() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 72
def create_revision_file_command
  source.create_revision_file_command(paths.active_revision)
end
deploy() click to toggle source

default task

# File lib/engineyard-serverside/deploy.rb, line 17
def deploy
  shell.status "Starting deploy at #{shell.start_time.asctime}"
  update_repository_cache
  cached_deploy
end
disable_maintenance_page() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 116
def disable_maintenance_page
  maintenance.conditionally_disable
end
enable_maintenance_page() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 112
def enable_maintenance_page
  maintenance.conditionally_enable
end
ensure_git_ssh_wrapper() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 156
def ensure_git_ssh_wrapper
  ENV['GIT_SSH'] = ssh_executable.to_s
end
ensure_ownership(*targets) click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 439
def ensure_ownership(*targets)
  sudo %|find #{targets.join(' ')} \\( -not -user #{config.user} -or -not -group #{config.group} \\) -exec chown #{config.user}:#{config.group} "{}" +|
end
gc_repository_cache() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 68
def gc_repository_cache
  source.gc_repository_cache if config.gc?
end
generate_ssh_wrapper() click to toggle source

We specify ‘IdentitiesOnly’ to avoid failures on systems with > 5 private keys available. We set UserKnownHostsFile to /dev/null because StrickHostKeyChecking no doesn’t ignore existing entries in known_hosts; we want to actively ignore all such. Learned this at lists.mindrot.org/pipermail/openssh-unix-dev/2009-February/027271.html (Thanks Jim L.)

# File lib/engineyard-serverside/deploy.rb, line 175
      def generate_ssh_wrapper
        path = paths.ssh_wrapper
        <<-SCRIPT
mkdir -p #{path.dirname}
[ -x #{path} ] || cat > #{path} <<'SSH'
#!/bin/sh
unset SSH_AUTH_SOCK

# Filter command mangling by git when using `GIT_SSH` and the string `plink`
command=$(echo "$*" | sed -e "s/^-batch //")
ssh -o CheckHostIP=no -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o LogLevel=INFO -o IdentityFile=#{paths.deploy_key} -o IdentitiesOnly=yes ${command}
SSH
chmod 0700 #{path}
        SCRIPT
      end
install_dependencies() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 100
def install_dependencies
  dependency_manager.install
end
migrate() click to toggle source

task

# File lib/engineyard-serverside/deploy.rb, line 257
def migrate
  return unless config.migrate?
  @migrations_reached = true
  cmd = "cd #{paths.active_release} && PATH=#{paths.binstubs}:$PATH #{config.framework_envs} #{config.migration_command}"
  roles :app_master, :solo do
    shell.status "Migrating: #{cmd}"
    run(cmd)
  end
end
new_release(&block) click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 267
def new_release(&block)
  copy_repository_cache
  with_failed_release_cleanup do
    shell.status "Ensuring proper ownership."
    ensure_ownership(paths.active_release)
    block.call
  end
end
push_code() click to toggle source

task

# File lib/engineyard-serverside/deploy.rb, line 127
def push_code
  shell.status "Pushing code to all servers"
  servers.remote.run_for_each do |server|
    server.sync_directory_command(paths.repository_cache)
  end
end
rails_application?() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 92
def rails_application?
  dependency_manager.rails_version
end
restart() click to toggle source

task

# File lib/engineyard-serverside/deploy.rb, line 135
def restart
  @restart_failed = true
  if config.restart_groups.to_i > 1
    shell.status "Restarting app servers in #{config.restart_groups.to_i} groups"
  else
    shell.status "Restarting app servers"
  end

  servers.roles(:app_master, :app, :solo).in_groups(config.restart_groups.to_i) do |group|
    shell.substatus "Restarting #{group.size} server#{group.size == 1 ? '' : 's'}"
    group.run(restart_command)
  end

  shell.status "Finished restarting app servers"
  @restart_failed = false
end
restart_command() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 152
def restart_command
  %{LANG="en_US.UTF-8" /engineyard/bin/app_#{config.app} deploy}
end
restart_with_maintenance_page() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 104
def restart_with_maintenance_page
  load_ey_yml
  require_custom_tasks
  enable_maintenance_page
  restart
  disable_maintenance_page
end
rollback() click to toggle source

task

# File lib/engineyard-serverside/deploy.rb, line 229
def rollback
  if config.rollback_paths!
    begin
      load_ey_yml
      require_custom_tasks
      rolled_back_release = paths.latest_release
      shell.status "Rolling back to previous release: #{short_log_message(config.active_revision)}"
      abort_on_bad_paths_in_release_directory
      run_with_callbacks(:symlink)
      sudo "rm -rf #{rolled_back_release}"
      bundle
      shell.status "Restarting with previous release."
      enable_maintenance_page
      run_with_callbacks(:restart)
      disable_maintenance_page
      shell.status "Finished rollback at #{Time.now.asctime}"
    rescue Exception
      shell.status "Failed to rollback at #{Time.now.asctime}"
      puts_deploy_failure
      raise
    end
  else
    shell.fatal "Already at oldest release, nothing to roll back to."
    exit(1)
  end
end
run_with_callbacks(task) click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 120
def run_with_callbacks(task)
  callback("before_#{task}")
  send(task)
  callback("after_#{task}")
end
setup_services() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 288
      def setup_services
        shell.status "Setting up external services."
        previously_configured_services = config.configured_services

        begin
          sudo(config.services_check_command)
        rescue EY::Serverside::RemoteFailure
          shell.info "Could not setup services. Upgrade your environment to get services configuration."
          return
        end

        begin
          sudo(config.services_setup_command)
        rescue EY::Serverside::RemoteFailure => e
          if previously_configured_services
            shell.warning <<-WARNING
External services configuration not updated. Using previous version.
Deploy again if your services configuration appears incomplete or out of date.
#{e}
            WARNING
          end
        end

        if services = config.configured_services
          shell.status "Services configured: #{services.join(', ')}"
          dependency_manager.show_ey_config_instructions
        end
      end
setup_shared_tmp() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 376
      def setup_shared_tmp
        if config.shared_tmp?
          shell.substatus "Creating and symlinking shared tmp directory if empty"
          run <<-SETUP_TMP
if [ -e "#{paths.active_tmp}" ] && [ ! "$(ls #{paths.active_tmp} 2>&1)" ]; then
  rm -rf #{paths.active_tmp};
fi;
if [ ! -e "#{paths.active_tmp}" ]; then
  mkdir -p #{paths.shared_tmp} && ln -nfs #{paths.shared_tmp} #{paths.active_tmp};
fi
          SETUP_TMP

          unless paths.active_tmp.symlink?
            note = "This application's repository has tmp/ with contents committed.\n"
            if config.precompile_assets?
              note << "Asset precompile speed can be increased by sharing tmp across releases.\n"
            end
            note << <<-NOTICE
Enable shared tmp with the follow git command (if applicable):

  $ git rm -r tmp/* && echo '*' > tmp/.gitignore

Or, disable this feature via ey.yml configuration as follows:

defaults:
  shared_tmp: false
            NOTICE
            shell.notice note
          end
        else
          shell.substatus "Prepare shared pids directory"
          run "rm -rf #{paths.active_tmp}/pids"
        end
      end
setup_sqlite3_if_necessary() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 317
      def setup_sqlite3_if_necessary
        if dependency_manager.uses_sqlite3?
          shell.status "Setting up SQLite3 Database for compatibility with application's chosen adapter"
          shell.warning "SQLite3 cannot persist across servers. Please upgrade to a supported database."

          shell.substatus "Create databases directory if needed"
          run "mkdir -p #{paths.shared}/databases"

          shell.substatus "Create SQLite database if needed"
          run "touch #{paths.shared}/databases/#{config.framework_env}.sqlite3"

          shell.substatus "Create config directory if needed"
          run "mkdir -p #{paths.active_release_config}"

          shell.substatus "Generating SQLite config"
          run <<-WRAP
cat > #{paths.shared_config}/database.sqlite3.yml<<'YML'
#{config.framework_env}:
  adapter: sqlite3
  database: #{paths.shared}/databases/#{config.framework_env}.sqlite3
  pool: 5
  timeout: 5000
YML
          WRAP

          shell.substatus "Symlink database.yml"
          run "ln -nfs #{paths.shared_config}/database.sqlite3.yml #{paths.active_release_config}/database.yml"
        end
      end
short_log_message(revision) click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 76
def short_log_message(revision)
  source.short_log_message(revision)
end
source() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 459
def source
  ensure_git_ssh_wrapper
  @source ||= config.source(shell)
end
ssh_executable() click to toggle source

create ssh wrapper on all servers

# File lib/engineyard-serverside/deploy.rb, line 161
def ssh_executable
  @ssh_executable ||= begin
                        roles :app_master, :app, :solo, :util do
                          run(generate_ssh_wrapper)
                        end
                        paths.ssh_wrapper
                      end
end
unchanged_diff_between_revisions?(previous_revision, active_revision, asset_dependencies) click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 80
def unchanged_diff_between_revisions?(previous_revision, active_revision, asset_dependencies)
  source.same?(previous_revision, active_revision, asset_dependencies)
end
update_repository_cache() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 64
def update_repository_cache
  source.update_repository_cache
end

Protected Instance Methods

compile_assets() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 512
def compile_assets
  RailsAssets.detect_and_compile(config, shell, self)
end
dependency_manager() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 507
def dependency_manager
  ensure_git_ssh_wrapper
  @dependency_manager ||= DependencyManager.new(servers, config, shell, self)
end
maintenance() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 503
def maintenance
  @maintenance ||= Maintenance.new(servers, config, shell)
end
puts_deploy_failure() click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 471
def puts_deploy_failure
  if @cleanup_failed
    shell.notice "[Relax] Your site is running new code, but clean up of old deploys failed."
  elsif maintenance.up?
    message = "[Attention] Maintenance page still up, consider the following before removing:\n"
    message << " * Deploy hooks ran. This might cause problems for reverting to old code.\n" if @callbacks_reached
    message << " * Migrations ran. This might cause problems for reverting to old code.\n" if @migrations_reached
    if @symlink_changed
      message << " * Your new code is symlinked as current.\n"
    else
      message << " * Your old code is still symlinked as current.\n"
    end
    message << " * Application servers failed to restart.\n" if @restart_failed
    message << "\n"
    message << "Need help? File a ticket for support.\n"
    shell.notice message
  else
    shell.notice "[Relax] Your site is still running old code and nothing destructive has occurred."
  end
end
serverside_bin() click to toggle source

FIXME: Legacy method, warn and remove.

# File lib/engineyard-serverside/deploy.rb, line 467
def serverside_bin
  About.binary
end
with_failed_release_cleanup() { || ... } click to toggle source
# File lib/engineyard-serverside/deploy.rb, line 492
def with_failed_release_cleanup
  yield
rescue Exception => e
  shell.status "Release #{paths.active_release} failed, saving release to #{paths.releases_failed}."
  run "mkdir -p #{paths.releases_failed}"
  ensure_ownership(paths.active_release, paths.releases_failed)
  run "mv #{paths.active_release} #{paths.releases_failed}"
  clean_release_directory(paths.releases_failed, config.keep_failed_releases.to_i)
  raise e
end