Created
January 30, 2015 21:05
-
-
Save jdrago999/6f93c3b95423fa9bf8c7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/ruby2.0 | |
class Proxy | |
instance_methods.each do |m| | |
undef_method m unless m =~ /(^__|^send$|^object_id$)/ | |
end | |
def initialize(*targets) | |
@targets = targets | |
end | |
protected | |
def method_missing(name, *args, &block) | |
@targets.map do |target| | |
target.public_send(name, *args, &block) | |
end | |
end | |
end | |
log_file_path = "/tmp/codedeploy-agent.update.log" | |
require 'logger' | |
if($stdout.isatty) | |
# if we are being run in a terminal, log to stdout and the log file. | |
@log = Logger.new(Proxy.new(File.open(log_file_path, 'a+'), $stdout)) | |
else | |
# keep at most 2MB of old logs rotating out 1MB at a time | |
@log = Logger.new(log_file_path, 2, 1048576) | |
# make sure anything coming out of ruby ends up in the log file | |
$stdout.reopen(log_file_path, 'a+') | |
$stderr.reopen(log_file_path, 'a+') | |
end | |
@log.level = Logger::INFO | |
begin | |
# use the gems bundled with the agent if present | |
Gem.use_paths(nil, Gem.path << "/opt/codedeploy-agent/vendor") | |
require 'fileutils' | |
require 'openssl' | |
require 'open-uri' | |
require 'uri' | |
require 'json' | |
def usage | |
print <<EOF | |
install <package-type> | |
package-type: 'rpm', 'deb', or 'auto' | |
Installs fetches the latest package version of the specified type and | |
installs it. rpms are installed with yum; debs are installed using gdebi. | |
This program is invoked automatically to update the agent once per day using | |
the same package manager the codedeploy-agent is initially installed with. | |
To use this script for a hands free install on any system specify a package | |
type of 'auto'. This will detect if yum or gdebi is present on the system | |
and select the one present if possible. If both rpm and deb package managers | |
are detected the automatic detection will abort | |
When using the automatic setup, if the system has apt-get but not gdebi, | |
the gdebi will be installed using apt-get first. | |
EOF | |
end | |
def run_command(*args) | |
exit_ok = system(*args) | |
$stdout.flush | |
$stderr.flush | |
@log.debug("Exit code: #{$?.exitstatus}") | |
return exit_ok | |
end | |
def get_ec2_metadata_region | |
begin | |
uri = URI.parse('http://169.254.169.254/latest/meta-data/placement/availability-zone') | |
az = uri.read(:read_timeout => 120) | |
az.strip | |
rescue OpenURI::HTTPError => e | |
@log.warn("Could not get region from EC2 metadata service at '#{uri.to_s}'") | |
end | |
if (az !~ /[a-z]{2}-[a-z]+-\d+[a-z]/) | |
@log.warn("Invalid availability zone name: '#{az}'.") | |
nil | |
else | |
az.chop | |
end | |
end | |
def get_region | |
@log.info('Checking AWS_REGION environment variable for region information...') | |
region = ENV['AWS_REGION'] | |
return region if region | |
@log.info('Checking EC2 metadata service for region information...') | |
region = get_ec2_metadata_region | |
return region if region | |
@log.info('Using fail-safe default region: us-east-1') | |
return 'us-east-1' | |
end | |
def get_s3_uri(region, bucket, key) | |
if (region == 'us-east-1') | |
URI.parse("https://s3.amazonaws.com/#{bucket}/#{key}") | |
else | |
URI.parse("https://s3-#{region}.amazonaws.com/#{bucket}/#{key}") | |
end | |
end | |
def get_package_from_s3(region, bucket, key, package_file) | |
@log.info("Downloading package from bucket #{bucket} and key #{key}...") | |
uri = get_s3_uri(region, bucket, key) | |
# stream package file to disk | |
begin | |
File.open(package_file, 'w+b') do |file| | |
uri.open(:ssl_verify_mode => OpenSSL::SSL::VERIFY_PEER, :redirect => true, :read_timeout => 120) do |s3| | |
file.write(s3.read) | |
end | |
end | |
rescue OpenURI::HTTPError => e | |
@log.error("Could not find package to download at '#{uri.to_s}'") | |
exit(1) | |
end | |
end | |
def get_version_file_from_s3(region, bucket, key) | |
@log.info("Downloading version file from bucket #{bucket} and key #{key}...") | |
uri = get_s3_uri(region, bucket, key) | |
begin | |
version_string = uri.read(:ssl_verify_mode => OpenSSL::SSL::VERIFY_PEER, :redirect => true, :read_timeout => 120) | |
JSON.parse(version_string) | |
rescue OpenURI::HTTPError => e | |
@log.error("Could not find version file to download at '#{uri.to_s}'") | |
exit(1) | |
end | |
end | |
def install_from_s3(region, bucket, version_file_key, type, install_cmd) | |
version_data = get_version_file_from_s3(region, bucket, version_file_key) | |
package_key = version_data[type] | |
package_base_name = package_key.split('/')[-1] # base name for the key in S3 | |
package_file = "/tmp/#{package_base_name}" | |
get_package_from_s3(region, bucket, package_key, package_file) | |
install_cmd << package_file | |
@log.info("Executing `#{install_cmd.join(" ")}`...") | |
if (!run_command(*install_cmd)) | |
@log.error("Error installing #{package_file}.") | |
FileUtils.rm(package_file) | |
exit(1) | |
end | |
FileUtils.rm(package_file) | |
end | |
def do_sanity_check(cmd) | |
@log.info("Waiting for a while before I check for a running agent") | |
sleep(3 * 60) | |
res = run_command(cmd, 'codedeploy-agent', 'status') | |
if (res.nil? || res == false) | |
@log.info("No codedeploy agent seems to be running. Starting the agent.") | |
run_command(cmd, 'codedeploy-agent', 'start-no-update') | |
end | |
end | |
if (Process.uid != 0) | |
@log.error('Must run as root to install packages') | |
exit(1) | |
end | |
@log.info("Starting update check.") | |
if (ARGV.length > 1) | |
usage | |
@log.error('Too many arguments.') | |
exit(1) | |
elsif (ARGV.length < 1) | |
usage | |
@log.error('Expected package type as argument.') | |
exit(1) | |
end | |
@type = ARGV[0].downcase; | |
if (@type == 'auto') | |
@log.info('Attempting to automatically detect supported package manager type for system...') | |
has_yum = run_command('which yum >/dev/null 2>/dev/null') | |
has_apt_get = run_command('which apt-get >/dev/null 2>/dev/null') | |
has_gdebi = run_command('which gdebi >/dev/null 2>/dev/null') | |
has_zypper = run_command('which zypper >/dev/null 2>/dev/null') | |
if (has_yum && (has_apt_get || has_gdebi)) | |
@log.error('Detected both supported rpm and deb package managers. Please specify which package type to use manually.') | |
exit(1) | |
end | |
if(has_yum) | |
@type = 'rpm' | |
elsif(has_zypper) | |
@type = 'zypper' | |
elsif(has_gdebi) | |
@type = 'deb' | |
elsif(has_apt_get) | |
@type = 'deb' | |
@log.warn('apt-get found but no gdebi. Installing gdebi with `apt-get install gdebi -y`...') | |
#use -y to answer yes to confirmation prompts | |
if(!run_command('/usr/bin/apt-get', 'install', 'gdebi', '-y')) | |
@log.error('Could not install gdebi.') | |
exit(1) | |
end | |
else | |
@log.error('Could not detect any supported package managers.') | |
exit(1) | |
end | |
end | |
region = get_region | |
bucket = "aws-codedeploy-#{region}" | |
version_file_key = 'latest/VERSION' | |
case @type | |
when 'help' | |
usage | |
when 'rpm' | |
#use -y to answer yes to confirmation prompts | |
install_cmd = ['/usr/bin/yum', '-y', 'localinstall'] | |
install_from_s3(region, bucket, version_file_key, @type, install_cmd) | |
do_sanity_check('/sbin/service') | |
when 'deb' | |
#use -n for non-interactive mode | |
#use -o to not overwrite config files unless they have not been changed | |
install_cmd = ['/usr/bin/gdebi', '-n', '-o', 'Dpkg::Options::="--force-confdef"', '-o', 'Dpg::Options::="--force-conffold"'] | |
install_from_s3(region, bucket, version_file_key, @type, install_cmd) | |
do_sanity_check('/usr/sbin/service') | |
when 'zypper' | |
#use -n for non-interactive mode | |
install_cmd = ['/usr/bin/zypper', 'install', '-n'] | |
install_from_s3(region, bucket, version_file_key, 'rpm', install_cmd) | |
else | |
@log.error("Unsupported package type '#{@type}'") | |
exit(1) | |
end | |
@log.info("Update check complete.") | |
@log.info("Stopping updater.") | |
rescue SystemExit => e | |
# don't log exit() as an error | |
raise e | |
rescue Exception => e | |
# make sure all unhandled exceptions are logged to the log | |
@log.error("Unhandled exception: #{e.inspect}") | |
e.backtrace.each do |line| | |
@log.error(" at " + line) | |
end | |
exit(1) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment