Skip to content

V1.3.1 Release #275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jan 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Please do the build steps mentioned above before running the integration test.
The integration test creates the following
* An IAM role "codedeploy-agent-integ-test-deployment-role" if it doesn't exist
* An IAM role "codedeploy-agent-integ-test-instance-role" if it doesn't exist
* An IAM user "codedeploy-agent-integ-test-instance-user" if it doesn't exist. (Access key will be recreated.)
* A CodeDeploy application
* Startup the codedeploy agent on your host
* A CodeDeploy deployment group with your host in it
Expand Down
198 changes: 144 additions & 54 deletions bin/install
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
# than 2.0. Testing on multiple Ruby versions is required for
# changes to this part of the code.
##################################################################
require 'json'

class Proxy
instance_methods.each do |m|
undef_method m unless m =~ /(^__|^send$|^object_id$)/
Expand Down Expand Up @@ -43,48 +41,119 @@ end
@log.level = Logger::INFO

require 'net/http'
require 'json'

TOKEN_PATH = '/latest/api/token'
DOCUMENT_PATH = '/latest/dynamic/instance-identity/document'
# This class is copied (almost directly) from lib/instance_metadata.rb
# It is not loaded as the InstanceMetadata makes additional assumptions
# about the runtime that cannot be satisfied at install time, hence the
# trimmed copy.
class IMDS
IP_ADDRESS = '169.254.169.254'
TOKEN_PATH = '/latest/api/token'
BASE_PATH = '/latest/meta-data'
IDENTITY_DOCUMENT_PATH = '/latest/dynamic/instance-identity/document'
DOMAIN_PATH = '/latest/meta-data/services/domain'

def self.imds_supported?
imds_v2? || imds_v1?
end

def self.imds_v1?
begin
get_request(BASE_PATH) { |response|
return response.kind_of? Net::HTTPSuccess
}
rescue
false
end
end

def self.imds_v2?
begin
put_request(TOKEN_PATH) { |token_response|
(token_response.kind_of? Net::HTTPSuccess) && get_request(BASE_PATH, token_response.body) { |response|
return response.kind_of? Net::HTTPSuccess
}
}
rescue
false
end
end

class IMDSV2
def self.region
doc['region'].strip
begin
identity_document()['region'].strip
rescue
nil
end
end

def self.domain
begin
get_instance_metadata(DOMAIN_PATH).strip
rescue
nil
end
end

def self.identity_document
# JSON is lazy loaded to ensure we dont break older ruby runtimes
require 'json'
JSON.parse(get_instance_metadata(IDENTITY_DOCUMENT_PATH).strip)
end

private
def self.get_instance_metadata(path)
begin
token = put_request(TOKEN_PATH)
get_request(path, token)
rescue
get_request(path)
end
end


private
def self.http_request(request)
Net::HTTP.start('169.254.169.254', 80, :read_timeout => 120, :open_timeout => 120) do |http|
Net::HTTP.start(IP_ADDRESS, 80, :read_timeout => 10, :open_timeout => 10) do |http|
response = http.request(request)
if response.code.to_i != 200
if block_given?
yield(response)
elsif response.kind_of? Net::HTTPSuccess
response.body
else
raise "HTTP error from metadata service: #{response.message}, code #{response.code}"
end
return response.body
end
end

def self.put_request(path)
def self.put_request(path, &block)
request = Net::HTTP::Put.new(path)
request['X-aws-ec2-metadata-token-ttl-seconds'] = '21600'
http_request(request)
http_request(request, &block)
end

def self.get_request(path, token = nil)
def self.get_request(path, token = nil, &block)
request = Net::HTTP::Get.new(path)
unless token.nil?
request['X-aws-ec2-metadata-token'] = token
end
http_request(request)
http_request(request, &block)
end
end

def self.doc
begin
token = put_request(TOKEN_PATH)
JSON.parse(get_request(DOCUMENT_PATH, token).strip)
rescue
JSON.parse(get_request(DOCUMENT_PATH).strip)
end
class S3Bucket
# Split out as older versions of ruby dont like multi entry attr
attr :domain
attr :region
attr :bucket
def initialize(domain, region, bucket)
@domain = domain
@region = region
@bucket = bucket
end

def object_uri(object_key)
URI.parse("https://#{@bucket}.s3.#{@region}.#{@domain}/#{object_key}")
end
end

Expand Down Expand Up @@ -197,6 +266,7 @@ EOF
@sanity_check = true
when '--help'
usage
exit(0)
when '--re-execed'
@reexeced = true
when '--proxy'
Expand Down Expand Up @@ -233,13 +303,19 @@ EOF
end
end

parse_args()

# Be helpful when 'help' was used but not '--help'
if @type == 'help'
usage
exit(0)
end

if (Process.uid != 0)
@log.error('Must run as root to install packages')
exit(1)
end

parse_args()

########## Force running as Ruby 2.x or fail here ##########
ruby_interpreter_path = check_ruby_version_and_symlink
force_ruby2x(ruby_interpreter_path)
Expand All @@ -252,13 +328,17 @@ EOF
return exit_ok
end

def get_ec2_metadata_region
begin
return IMDSV2.region
rescue => error
@log.warn("Could not get region from EC2 metadata service at '#{error.message}'")
return nil
def get_ec2_metadata_property(property)
if IMDS.imds_supported?
begin
return IMDS.send(property)
rescue => error
@log.warn("Could not get #{property} from EC2 metadata service at '#{error.message}'")
end
else
@log.warn("EC2 metadata service unavailable...")
end
return nil
end

def get_region
Expand All @@ -267,27 +347,36 @@ EOF
return region if region

@log.info('Checking EC2 metadata service for region information...')
region = get_ec2_metadata_region
region = get_ec2_metadata_property(: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://#{bucket}.s3.amazonaws.com/#{key}")
elsif (region.split("-")[0] == 'cn')
URI.parse("https://#{bucket}.s3.#{region}.amazonaws.com.cn/#{key}")
else
URI.parse("https://#{bucket}.s3.#{region}.amazonaws.com/#{key}")
def get_domain(fallback_region = nil)
@log.info('Checking AWS_DOMAIN environment variable for domain information...')
domain = ENV['AWS_DOMAIN']
return domain if domain

@log.info('Checking EC2 metadata service for domain information...')
domain = get_ec2_metadata_property(:domain)
return domain if domain

domain = 'amazonaws.com'
if !fallback_region.nil? && fallback_region.split("-")[0] == 'cn'
domain = 'amazonaws.com.cn'
end

@log.info("Using fail-safe default domain: #{domain}")
return domain
end

def get_package_from_s3(region, bucket, key, package_file)
@log.info("Downloading package from bucket #{bucket} and key #{key}...")
def get_package_from_s3(s3_bucket, key, package_file)
@log.info("Downloading package from bucket #{s3_bucket.bucket} and key #{key}...")

uri = get_s3_uri(region, bucket, key)
uri = s3_bucket.object_uri(key)
@log.info("Endpoint: #{uri}")

# stream package file to disk
retries ||= 0
Expand All @@ -309,10 +398,11 @@ EOF
end
end

def get_version_file_from_s3(region, bucket, key)
@log.info("Downloading version file from bucket #{bucket} and key #{key}...")
def get_version_file_from_s3(s3_bucket, key)
@log.info("Downloading version file from bucket #{s3_bucket.bucket} and key #{key}...")

uri = get_s3_uri(region, bucket, key)
uri = s3_bucket.object_uri(key)
@log.info("Endpoint: #{uri}")

begin
require 'json'
Expand All @@ -325,13 +415,13 @@ end
end
end

def install_from_s3(region, bucket, package_key, install_cmd)
def install_from_s3(s3_bucket, package_key, install_cmd)
package_base_name = File.basename(package_key)
package_extension = File.extname(package_base_name)
package_name = File.basename(package_base_name, package_extension)
package_file = Tempfile.new(["#{package_name}.tmp-", package_extension]) # unique file with 0600 permissions

get_package_from_s3(region, bucket, package_key, package_file)
get_package_from_s3(s3_bucket, package_key, package_file)
package_file.close

install_cmd << package_file.path
Expand All @@ -358,10 +448,10 @@ end
end
end

def get_target_version(target_version, type, region, bucket)
def get_target_version(target_version, type, s3_bucket)
if target_version.nil?
version_file_key = 'latest/LATEST_VERSION'
version_data = get_version_file_from_s3(region, bucket, version_file_key)
version_data = get_version_file_from_s3(s3_bucket, version_file_key)
if version_data.include? type
return version_data[type]
else
Expand Down Expand Up @@ -408,14 +498,14 @@ end
end
end

region = get_region
region = get_region()
domain = get_domain(region)
bucket = "aws-codedeploy-#{region}"
s3_bucket = S3Bucket.new(domain, region, bucket)

target_version = get_target_version(@target_version_arg, @type, region, bucket)
target_version = get_target_version(@target_version_arg, @type, s3_bucket)

case @type
when 'help'
usage
when 'rpm'
running_version = `rpm -q codedeploy-agent`
running_version.strip!
Expand All @@ -424,7 +514,7 @@ end
else
#use -y to answer yes to confirmation prompts
install_cmd = ['/usr/bin/yum', '-y', 'localinstall']
install_from_s3(region, bucket, target_version, install_cmd)
install_from_s3(s3_bucket, target_version, install_cmd)
do_sanity_check('/sbin/service')
end
when 'deb'
Expand All @@ -443,13 +533,13 @@ end
#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', 'Dkpg::Options::=--force-confold']
install_from_s3(region, bucket, target_version, install_cmd)
install_from_s3(s3_bucket, target_version, install_cmd)
do_sanity_check('/usr/sbin/service')
end
when 'zypper'
#use -n for non-interactive mode
install_cmd = ['/usr/bin/zypper', 'install', '-n']
install_from_s3(region, bucket, target_version, install_cmd)
install_from_s3(s3_bucket, target_version, install_cmd)
else
@log.error("Unsupported package type '#{@type}'")
exit(1)
Expand Down
Loading