lib/instance_agent/plugins/codedeploy/installer.rb
require 'instance_agent/plugins/codedeploy/install_instruction'
module InstanceAgent
module Plugins
module CodeDeployPlugin
# Manages install and cleanup files. Also generates and executes
# install instructions based on the files section of the
# application specification file.
class Installer
attr_reader :deployment_archive_dir
attr_reader :deployment_instructions_dir
attr_accessor :file_exists_behavior
def initialize(opts = {})
raise "the deployment_archive_dir option is required" if
opts[:deployment_archive_dir].nil?
raise "the deployment_instructions_dir option is required" if
opts[:deployment_instructions_dir].nil?
raise "the file_exists_behavior option is required" if
opts[:file_exists_behavior].nil?
@deployment_archive_dir = opts[:deployment_archive_dir]
@deployment_instructions_dir = opts[:deployment_instructions_dir]
@file_exists_behavior = opts[:file_exists_behavior]
end
def install(deployment_group_id, application_specification)
cleanup_file = File.join(deployment_instructions_dir, "#{deployment_group_id}-cleanup")
if File.exists?(cleanup_file)
commands = InstanceAgent::Plugins::CodeDeployPlugin::InstallInstruction.parse_remove_commands(File.read(cleanup_file))
commands.each do |cmd|
cmd.execute
end
commands.clear
FileUtils.rm(cleanup_file)
end
instructions = generate_instructions(application_specification)
install_file = File.join(deployment_instructions_dir, "#{deployment_group_id}-install.json")
File.open(install_file, "w") do |f|
f.write(instructions.to_json)
end
File.open(cleanup_file, "w") do |f|
instructions.command_array.each do |cmd|
cmd.execute(f)
end
end
#Unlink references to the CommandBuilder instance that was yielded to the Proc object(code block) in generate_instructions()
instructions.cleanup
instructions = nil
end
private
def generate_instructions(application_specification)
InstanceAgent::Plugins::CodeDeployPlugin::InstallInstruction.generate_instructions() do |i|
application_specification.files.each do |fi|
absolute_source_path = File.join(deployment_archive_dir, fi.source)
file_exists_behavior = application_specification.respond_to?(:file_exists_behavior) && application_specification.file_exists_behavior ? application_specification.file_exists_behavior : @file_exists_behavior
log(:debug, "generating instructions for copying #{fi.source} to #{fi.destination}")
if File.directory?(absolute_source_path)
fill_in_missing_ancestors(i, fi.destination)
generate_directory_copy(i, absolute_source_path, fi.destination, file_exists_behavior)
else
file_destination = File.join(fi.destination, File.basename(absolute_source_path))
fill_in_missing_ancestors(i, file_destination)
generate_normal_copy(i, absolute_source_path, file_destination, file_exists_behavior)
end
end
(application_specification.permissions || []).each do |permission|
object = permission.object
log(:debug, "generating instructions for setting permissions on object #{object}")
log(:debug, "it is an existing directory - #{File.directory?(object)}")
if i.copying_file?(object)
if permission.type.include?("file")
log(:debug, "found matching file #{object} to set permissions on")
permission.validate_file_permission
permission.validate_file_acl(object)
i.set_permissions(object, permission)
end
elsif (i.making_directory?(object) || File.directory?(object))
log(:debug, "found matching directory #{object} to search for objects to set permissions on")
i.find_matches(permission).each do|match|
log(:debug, "found matching object #{match} to set permissions on")
i.set_permissions(match, permission)
end
end
end
end
end
private
def generate_directory_copy(i, absolute_source_path, destination, file_exists_behavior)
unless File.directory?(destination)
i.mkdir(destination)
end
(Dir.entries(absolute_source_path) - [".", ".."]).each do |entry|
entry = entry.force_encoding("UTF-8");
absolute_source_path = absolute_source_path.force_encoding("UTF-8");
absolute_entry_path = File.join(absolute_source_path, entry)
entry_destination = File.join(destination, entry)
if File.directory?(absolute_entry_path)
generate_directory_copy(i, absolute_entry_path, entry_destination, file_exists_behavior)
else
generate_normal_copy(i, absolute_entry_path, entry_destination, file_exists_behavior)
end
end
end
private
def generate_normal_copy(i, absolute_source_path, destination, file_exists_behavior)
if File.exists?(destination)
case file_exists_behavior
when "DISALLOW"
raise "The deployment failed because a specified file already exists at this location: #{destination}"
when "OVERWRITE"
i.copy(absolute_source_path, destination)
when "RETAIN"
# neither generate copy command or fail the deployment
else
raise "The deployment failed because an invalid option was specified for fileExistsBehavior: #{file_exists_behavior}. Valid options include OVERWRITE, RETAIN, and DISALLOW."
end
else
i.copy(absolute_source_path, destination)
end
end
private
def fill_in_missing_ancestors(i, destination)
missing_ancestors = []
parent_dir = File.dirname(destination)
while !File.exists?(parent_dir) &&
parent_dir != "." && parent_dir != "/"
missing_ancestors.unshift(parent_dir)
parent_dir = File.dirname(parent_dir)
end
missing_ancestors.each do |dir|
i.mkdir(dir)
end
end
private
def description
self.class.to_s
end
private
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
end
end
end
end