class InstanceAgent::Plugins::CodeDeployPlugin::HookExecutor

Constants

CURRENT
LAST_SUCCESSFUL_DEPLOYMENT

Public Class Methods

new(arguments = {}) click to toggle source
# File lib/instance_agent/plugins/codedeploy/hook_executor.rb, line 64
def initialize(arguments = {})
  #check arguments
  raise "Lifecycle Event Required " if arguments[:lifecycle_event].nil?
  raise "Deployment ID required " if arguments[:deployment_id].nil?
  raise "Deployment Root Directory Required " if arguments[:deployment_root_dir].nil?
  raise "App Spec Path Required " if arguments[:app_spec_path].nil?
  raise "Application name required" if arguments[:application_name].nil?
  raise "Deployment Group name required" if arguments[:deployment_group_name].nil?
  @lifecycle_event = arguments[:lifecycle_event]
  @deployment_id = arguments[:deployment_id]
  @application_name = arguments[:application_name]
  @deployment_group_name = arguments[:deployment_group_name]
  @deployment_group_id = arguments[:deployment_group_id]
  @current_deployment_root_dir = arguments[:deployment_root_dir]
  select_correct_deployment_root_dir(arguments[:deployment_root_dir], arguments[:last_successful_deployment_dir])
  return if @deployment_root_dir.nil?
  @deployment_archive_dir = File.join(@deployment_root_dir, 'deployment-archive')
  @app_spec_path = arguments[:app_spec_path]
  parse_app_spec
  @hook_logging_mutex = Mutex.new
  @script_log = ScriptLog.new
  @child_envs={'LIFECYCLE_EVENT' => @lifecycle_event.to_s,
              'DEPLOYMENT_ID'   => @deployment_id.to_s,
              'APPLICATION_NAME' => @application_name,
              'DEPLOYMENT_GROUP_NAME' => @deployment_group_name,
              'DEPLOYMENT_GROUP_ID' => @deployment_group_id}
end

Public Instance Methods

execute() click to toggle source
# File lib/instance_agent/plugins/codedeploy/hook_executor.rb, line 92
def execute
  return if @app_spec.nil?
  if (hooks = @app_spec.hooks[@lifecycle_event]) &&
  !hooks.empty?
    create_script_log_file_if_needed do |script_log_file|
      log_script("LifecycleEvent - " + @lifecycle_event + "\n", script_log_file)
      hooks.each do |script|
        if(!File.exist?(script_absolute_path(script)))
          raise ScriptError.new(ScriptError::SCRIPT_MISSING_CODE, script.location, @script_log), 'Script does not exist at specified location: ' + File.expand_path(script_absolute_path(script))
        elsif(!InstanceAgent::Platform.util.script_executable?(script_absolute_path(script)))
          log :warn, 'Script at specified location: ' + script.location + ' is not executable.  Trying to make it executable.'
          begin
            FileUtils.chmod("+x", script_absolute_path(script))
          rescue
            raise ScriptError.new(ScriptError::SCRIPT_EXECUTABILITY_CODE, script.location, @script_log), 'Unable to set script at specified location: ' + script.location + ' as executable'
          end
        end
        begin
          execute_script(script, script_log_file)
        rescue Timeout::Error
          raise ScriptError.new(ScriptError::SCRIPT_TIMED_OUT_CODE, script.location, @script_log), 'Script at specified location: ' +script.location + ' failed to complete in '+script.timeout.to_s+' seconds'
        end
      end
    end
  end
  @script_log.log
end

Private Instance Methods

create_script_log_file_if_needed() { |script_log_file| ... } click to toggle source
# File lib/instance_agent/plugins/codedeploy/hook_executor.rb, line 158
def create_script_log_file_if_needed
  script_log_file_location = File.join(@current_deployment_root_dir, 'logs/scripts.log')
  if(!File.exists?(script_log_file_location))
    unless File.directory?(File.dirname(script_log_file_location))
      FileUtils.mkdir_p(File.dirname(script_log_file_location))
    end
    script_log_file = File.open(script_log_file_location, 'w')
  else
    script_log_file = File.open(script_log_file_location, 'a')
  end
  yield(script_log_file)
ensure
  script_log_file.close unless script_log_file.nil?
end
description() click to toggle source
# File lib/instance_agent/plugins/codedeploy/hook_executor.rb, line 208
def description
  self.class.to_s
end
execute_script(script, script_log_file) click to toggle source
# File lib/instance_agent/plugins/codedeploy/hook_executor.rb, line 121
def execute_script(script, script_log_file)
  script_command = InstanceAgent::Platform.util.prepare_script_command(script, script_absolute_path(script))
  log_script("Script - " + script.location + "\n", script_log_file)
  exit_status = 1
  signal = nil

  if !InstanceAgent::Platform.util.supports_process_groups?
    # The Windows port doesn't emulate process groups so don't try to use them here
    open3_options = {}
    signal = 'KILL' #It is up to the script to handle killing child processes it spawns.
  else
    open3_options = {:pgroup => true}
    signal = '-TERM' #kill the process group instead of pid
  end

  Open3.popen3(@child_envs, script_command, open3_options) do |stdin, stdout, stderr, wait_thr|
    stdin.close
    stdout_thread = Thread.new{stdout.each_line { |line| log_script("[stdout]" + line.to_s, script_log_file)}}
    stderr_thread = Thread.new{stderr.each_line { |line| log_script("[stderr]" + line.to_s, script_log_file)}}
    if !wait_thr.join(script.timeout)
      Process.kill(signal, wait_thr.pid)
      raise Timeout::Error
    end
    stdout_thread.join
    stderr_thread.join
    exit_status = wait_thr.value.exitstatus
  end
  if(exit_status != 0)
    script_error = 'Script at specified location: ' + script.location + ' failed with exit code ' + exit_status.to_s
    if(!script.runas.nil?)
      script_error = 'Script at specified location: ' + script.location + ' run as user ' + script.runas + ' failed with exit code ' + exit_status.to_s
    end
    raise ScriptError.new(ScriptError::SCRIPT_FAILED_CODE, script.location, @script_log), script_error
  end
end
log(severity, message) click to toggle source
# File lib/instance_agent/plugins/codedeploy/hook_executor.rb, line 213
def log(severity, message)
  raise ArgumentError, "Unknown severity #{severity.inspect}" unless InstanceAgent::Log::SEVERITIES.include?(severity.to_s)
  InstanceAgent::Log.send(severity.to_sym, "#{description}: #{message}")
end
log_script(message, script_log_file) click to toggle source
# File lib/instance_agent/plugins/codedeploy/hook_executor.rb, line 219
def log_script(message, script_log_file)
  @hook_logging_mutex.synchronize do
    @script_log.append_to_log(message)
    script_log_file.write(Time.now.to_s[0..-7] + ' ' + message)
    InstanceAgent::DeploymentLog.instance.log("[#{@deployment_id}]#{message.strip}") if InstanceAgent::Config.config[:enable_deployments_log]
    script_log_file.flush
  end
end
mapping_between_hooks_and_deployments() click to toggle source
# File lib/instance_agent/plugins/codedeploy/hook_executor.rb, line 195
def mapping_between_hooks_and_deployments
  {"BeforeELBRemove"=>LAST_SUCCESSFUL_DEPLOYMENT,
    "AfterELBRemove"=>LAST_SUCCESSFUL_DEPLOYMENT,
    "ApplicationStop"=>LAST_SUCCESSFUL_DEPLOYMENT,
    "BeforeInstall"=>CURRENT,
    "AfterInstall"=>CURRENT,
    "ApplicationStart"=>CURRENT,
    "BeforeELBAdd"=>CURRENT,
    "AfterELBAdd"=>CURRENT,
    "ValidateService"=>CURRENT}
end
parse_app_spec() click to toggle source
# File lib/instance_agent/plugins/codedeploy/hook_executor.rb, line 179
def parse_app_spec
  app_spec_location = File.join(@deployment_archive_dir, @app_spec_path)
  log(:debug, "Checking for app spec in #{app_spec_location}")
  @app_spec =  ApplicationSpecification::ApplicationSpecification.parse(File.read(app_spec_location))
end
script_absolute_path(script) click to toggle source
# File lib/instance_agent/plugins/codedeploy/hook_executor.rb, line 174
def script_absolute_path(script)
  File.join(@deployment_archive_dir, script.location)
end
select_correct_deployment_root_dir(current_deployment_root_dir, last_successful_deployment_root_dir) click to toggle source
# File lib/instance_agent/plugins/codedeploy/hook_executor.rb, line 186
def select_correct_deployment_root_dir(current_deployment_root_dir, last_successful_deployment_root_dir)
  @deployment_root_dir = current_deployment_root_dir
  hook_deployment_mapping = mapping_between_hooks_and_deployments
  if(hook_deployment_mapping[@lifecycle_event] == LAST_SUCCESSFUL_DEPLOYMENT && !File.exist?(File.join(@deployment_root_dir, 'deployment-archive')))
    @deployment_root_dir = last_successful_deployment_root_dir
  end
end