Skip to content

Instantly share code, notes, and snippets.

@yevgenko
Created June 9, 2012 21:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yevgenko/2902683 to your computer and use it in GitHub Desktop.
Save yevgenko/2902683 to your computer and use it in GitHub Desktop.
Deploy Hooks used for PHP Apps under AWS ElasticBeanstalk EC2 instances
#############################################################################
# AWS Elastic Beanstalk Host Manager
# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Amazon Software License (the “License”). You may
# not use this file except in compliance with the License. A copy of the
# License is located at
#
# http://aws.amazon.com/asl/
#
# or in the “license” file accompanying this file. This file is
# distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
require 'json'
module ElasticBeanstalk
module HostManager
module Applications
class PHPApplication < Application
class << self
attr_reader :web_root_dir, :deploy_dir, :pre_deploy_script, :deploy_script, :post_deploy_script, :error_start_index
end
# Directories, etc
@web_root_dir = '/var/www/html'
@deploy_dir = '/tmp/php-elasticbeanstalk-deployment'
@pre_deploy_script = '/tmp/php_pre_deploy_app.sh'
@deploy_script = '/tmp/php_deploy_app.sh'
@post_deploy_script = '/tmp/php_post_deploy_app.sh'
# For error messages, get the last 512 chars of the deployment output
@error_start_index = -512
def pre_deploy
application_version_url = @version_info.to_url
deploy_script_contents = <<-END_PRE_DEPLOY_SCRIPT
#!/bin/bash
set -e
function logmsg() {
echo $1
/usr/bin/logger $1
}
logmsg "Beginning pre-deployment"
logmsg "Cleaning up deploy dir"
/usr/bin/sudo /bin/rm -rf #{PHPApplication.deploy_dir}
/bin/mkdir -p #{PHPApplication.deploy_dir}
logmsg "Downloading #{application_version_url}"
DOWNLOAD_TIME=`/usr/bin/time -f %e /usr/bin/wget -v --tries=10 --retry-connrefused -o #{PHPApplication.deploy_dir}/wget.log -O #{PHPApplication.deploy_dir}/application.zip "#{application_version_url}" 2>&1`
DOWNLOAD_RATE=`grep -o '(\\(.*\\/s\\))' #{PHPApplication.deploy_dir}/wget.log | sed 's/[\\(\\)]//g'`
logmsg "Checking application digest"
/usr/bin/openssl dgst -md5 #{PHPApplication.deploy_dir}/application.zip > #{PHPApplication.deploy_dir}/application_digest
/bin/echo 'MD5(#{PHPApplication.deploy_dir}/application.zip)= #{@version_info.digest}' > #{PHPApplication.deploy_dir}/expected_digest
/usr/bin/cmp #{PHPApplication.deploy_dir}/application_digest #{PHPApplication.deploy_dir}/expected_digest
logmsg "Digest matched"
echo "{\\"AppDownloadTime\\":\\"$DOWNLOAD_TIME\\",\\"AppDownloadRate\\":\\"$DOWNLOAD_RATE\\"}"
END_PRE_DEPLOY_SCRIPT
HostManager.log "Starting PHP pre-deployment: Application version #{@version_info.version} from #{application_version_url}"
begin
::File.open(PHPApplication.pre_deploy_script, 'w') { |f|
f.write(deploy_script_contents)
}
::File.chmod(0755, PHPApplication.pre_deploy_script)
# Execute script, redirect stderr to stdout
output = `#{PHPApplication.pre_deploy_script} 2>&1`
# Raise an exception if script failed
raise 'PHP application pre-deployment script failed' if ($?.exitstatus != 0)
# Record deployment metrics from script
metrics = JSON.parse(output.split("\n")[-1].chomp)
# Convert download time from seconds to milliseconds and store metric
unless (metrics['AppDownloadTime'].nil?)
app_download_time = metrics['AppDownloadTime'].to_f * 1000
HostManager.log "Application Download Time (ms): #{app_download_time}"
HostManager.state.context[:metric].timings['AppDownloadTime'] = app_download_time unless HostManager.state.context[:metric].nil?
end
# Rate could be Mb/s, Kb/s, etc, convert all to Kb/s
unless (metrics['AppDownloadRate'].nil? || metrics['AppDownloadRate'][/[0-9]*/].nil?)
app_download_rate = metrics['AppDownloadRate'][/[0-9]*/].to_f
app_download_rate = app_download_rate * 1024 if metrics['AppDownloadRate'] =~ /MB/
HostManager.log "Application Download Rate (kb/s): #{app_download_rate}"
HostManager.state.context[:metric].counters['AppDownloadRate'] = app_download_rate unless HostManager.state.context[:metric].nil?
end
# Capture any Errno errors from the ::File operations
rescue SystemCallError
ex = ElasticBeanstalk::HostManager::DeployException.new('Failed to create application pre-deployment script')
raise ex
# All other exceptions, including the raised non-zero exit one go here
rescue
ex = ElasticBeanstalk::HostManager::DeployException.new("Failed application version #{@version_info.version} pre-deployment: #{$!}")
ex.output = output[PHPApplication.error_start_index..-1] || output
raise ex
end
end
def deploy
deploy_script_contents = <<-END_DEPLOY_SCRIPT
#!/bin/bash
set -e
function logmsg() {
echo $1
/usr/bin/logger $1
}
logmsg "Unzipping application"
/bin/mkdir -p #{PHPApplication.deploy_dir}/application
/bin/mkdir -p #{PHPApplication.deploy_dir}/backup
set +e
/usr/bin/unzip -o -qq #{PHPApplication.deploy_dir}/application.zip -d #{PHPApplication.deploy_dir}/application
if [ "$?" -ne 0 -a "$?" -ne 1 ]; then logmsg "Failed to unzip application"; exit 1; fi
set -e
logmsg "Fixing new app permissions"
/usr/bin/sudo /bin/chown -Rf elasticbeanstalk:elasticbeanstalk #{PHPApplication.deploy_dir}/application
/usr/bin/sudo /bin/chmod -Rf 0755 #{PHPApplication.deploy_dir}/application
/bin/find #{PHPApplication.deploy_dir}/application -type f -print0 | /usr/bin/xargs -0 /bin/chmod 0644
if [ "$(ls -A #{PHPApplication.web_root_dir})" ]; then
logmsg "Nuking the old web root"
/usr/bin/sudo rm -Rf #{PHPApplication.web_root_dir}/
/usr/bin/sudo mkdir -p #{PHPApplication.web_root_dir}/
fi
logmsg "Moving new web root into place"
/usr/bin/sudo mv -n #{PHPApplication.deploy_dir}/application/{,.}?* #{PHPApplication.web_root_dir}
logmsg "Deployment complete"
END_DEPLOY_SCRIPT
HostManager.log "Starting PHP deployment: Application version #{@version_info.version}"
begin
::File.open(PHPApplication.deploy_script, 'w') { |f|
f.write(deploy_script_contents)
}
::File.chmod(0755, PHPApplication.deploy_script)
# Execute script, redirect stderr to stdout
output = `#{PHPApplication.deploy_script} 2>&1`
# Raise an exception if script failed
raise 'PHP application deployment script failed' if ($?.exitstatus != 0)
HostManager.log 'PHP application successfully deployed'
# Capture any Errno errors from the ::File operations
rescue SystemCallError
ex = ElasticBeanstalk::HostManager::DeployException.new('Failed to create application deployment script')
raise ex
# All other exceptions, including the raised non-zero exit one go here
rescue
ex = ElasticBeanstalk::HostManager::DeployException.new("Failed to deploy application version #{@version_info.version}: #{$!}")
ex.output = output[PHPApplication.error_start_index..-1] || output
raise ex
end
end
def post_deploy
end
end # PHPApplication class
end
end
end
@rizz0
Copy link

rizz0 commented Sep 5, 2012

Will this script look for '/tmp/php_post_deploy_app.sh' when deploying, and execute it if found?

Looking for the easiest way to execute some commands when deployment is complete.

@yevgenko
Copy link
Author

@rizz0, first of all sorry for the late reply, missed your comment for some reason...
regarding your question, no, it won't, at the moment I took it from AWS EB Instance the post_deploy method wasn't implemented, as you can see in above script.

@yevgenko
Copy link
Author

I'm not quite sure if it trigger post_deploy hook at all, but if it, you could replace it with your custom code easily, e.g.:

def post_deploy
  deploy_script_contents = <<-END_DEPLOY_SCRIPT
  #!/bin/bash
  set -e

  function logmsg() {
  echo $1
  /usr/bin/logger $1
  }

  logmsg "Finalizing deployment"
  cd #{PHPApplication.web_root_dir}
  /usr/bin/sudo php -d memory_limit=256M composer.phar install
  logmsg "All Done!"
  END_DEPLOY_SCRIPT

  HostManager.log "Starting PHP deployment: Application version #{@version_info.version}"

  begin
    ::File.open(PHPApplication.post_deploy_script, 'w') { |f|
    f.write(deploy_script_contents)
    }

    ::File.chmod(0755, PHPApplication.post_deploy_script)

    # Execute script, redirect stderr to stdout
    output = `#{PHPApplication.post_deploy_script} 2>&1`

    # Raise an exception if script failed
    raise 'PHP application deployment script failed' if ($?.exitstatus != 0)

    HostManager.log 'PHP application successfully deployed'
  # Capture any Errno errors from the ::File operations
  rescue SystemCallError
    ex = ElasticBeanstalk::HostManager::DeployException.new('Failed to create application deployment script')
    raise ex
  # All other exceptions, including the raised non-zero exit one go here
  rescue
    ex = ElasticBeanstalk::HostManager::DeployException.new("Failed to deploy application version #{@version_info.version}: #{$!}")
    ex.output = output[PHPApplication.error_start_index..-1] || output
    raise ex
  end
end

I just took deploy method and modified script content a little bit.

Alternatively you could append any additional shell commands to the end of deploy_script_contents inside of deploy method as I did and that's just works.

@yevgenko
Copy link
Author

btw, this days you can use container_commands instead. No more need to maintain custom AMI just for deployment hooks ;)

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