55# than 2.0. Testing on multiple Ruby versions is required for
66# changes to this part of the code.
77##################################################################
8- require 'json'
9-
108class Proxy
119 instance_methods . each do |m |
1210 undef_method m unless m =~ /(^__|^send$|^object_id$)/
4341@log . level = Logger ::INFO
4442
4543require 'net/http'
46- require 'json'
4744
48- TOKEN_PATH = '/latest/api/token'
49- DOCUMENT_PATH = '/latest/dynamic/instance-identity/document'
45+ # This class is copied (almost directly) from lib/instance_metadata.rb
46+ # It is not loaded as the InstanceMetadata makes additional assumptions
47+ # about the runtime that cannot be satisfied at install time, hence the
48+ # trimmed copy.
49+ class IMDS
50+ IP_ADDRESS = '169.254.169.254'
51+ TOKEN_PATH = '/latest/api/token'
52+ BASE_PATH = '/latest/meta-data'
53+ IDENTITY_DOCUMENT_PATH = '/latest/dynamic/instance-identity/document'
54+ DOMAIN_PATH = '/latest/meta-data/services/domain'
55+
56+ def self . imds_supported?
57+ imds_v2? || imds_v1?
58+ end
59+
60+ def self . imds_v1?
61+ begin
62+ get_request ( BASE_PATH ) { |response |
63+ return response . kind_of? Net ::HTTPSuccess
64+ }
65+ rescue
66+ false
67+ end
68+ end
69+
70+ def self . imds_v2?
71+ begin
72+ put_request ( TOKEN_PATH ) { |token_response |
73+ ( token_response . kind_of? Net ::HTTPSuccess ) && get_request ( BASE_PATH , token_response . body ) { |response |
74+ return response . kind_of? Net ::HTTPSuccess
75+ }
76+ }
77+ rescue
78+ false
79+ end
80+ end
5081
51- class IMDSV2
5282 def self . region
53- doc [ 'region' ] . strip
83+ begin
84+ identity_document ( ) [ 'region' ] . strip
85+ rescue
86+ nil
87+ end
88+ end
89+
90+ def self . domain
91+ begin
92+ get_instance_metadata ( DOMAIN_PATH ) . strip
93+ rescue
94+ nil
95+ end
96+ end
97+
98+ def self . identity_document
99+ # JSON is lazy loaded to ensure we dont break older ruby runtimes
100+ require 'json'
101+ JSON . parse ( get_instance_metadata ( IDENTITY_DOCUMENT_PATH ) . strip )
54102 end
55103
104+ private
105+ def self . get_instance_metadata ( path )
106+ begin
107+ token = put_request ( TOKEN_PATH )
108+ get_request ( path , token )
109+ rescue
110+ get_request ( path )
111+ end
112+ end
113+
114+
56115 private
57116 def self . http_request ( request )
58- Net ::HTTP . start ( '169.254.169.254' , 80 , :read_timeout => 120 , :open_timeout => 120 ) do |http |
117+ Net ::HTTP . start ( IP_ADDRESS , 80 , :read_timeout => 10 , :open_timeout => 10 ) do |http |
59118 response = http . request ( request )
60- if response . code . to_i != 200
119+ if block_given?
120+ yield ( response )
121+ elsif response . kind_of? Net ::HTTPSuccess
122+ response . body
123+ else
61124 raise "HTTP error from metadata service: #{ response . message } , code #{ response . code } "
62125 end
63- return response . body
64126 end
65127 end
66128
67- def self . put_request ( path )
129+ def self . put_request ( path , & block )
68130 request = Net ::HTTP ::Put . new ( path )
69131 request [ 'X-aws-ec2-metadata-token-ttl-seconds' ] = '21600'
70- http_request ( request )
132+ http_request ( request , & block )
71133 end
72134
73- def self . get_request ( path , token = nil )
135+ def self . get_request ( path , token = nil , & block )
74136 request = Net ::HTTP ::Get . new ( path )
75137 unless token . nil?
76138 request [ 'X-aws-ec2-metadata-token' ] = token
77139 end
78- http_request ( request )
140+ http_request ( request , & block )
79141 end
142+ end
80143
81- def self . doc
82- begin
83- token = put_request ( TOKEN_PATH )
84- JSON . parse ( get_request ( DOCUMENT_PATH , token ) . strip )
85- rescue
86- JSON . parse ( get_request ( DOCUMENT_PATH ) . strip )
87- end
144+ class S3Bucket
145+ # Split out as older versions of ruby dont like multi entry attr
146+ attr :domain
147+ attr :region
148+ attr :bucket
149+ def initialize ( domain , region , bucket )
150+ @domain = domain
151+ @region = region
152+ @bucket = bucket
153+ end
154+
155+ def object_uri ( object_key )
156+ URI . parse ( "https://#{ @bucket } .s3.#{ @region } .#{ @domain } /#{ object_key } " )
88157 end
89158end
90159
197266 @sanity_check = true
198267 when '--help'
199268 usage
269+ exit ( 0 )
200270 when '--re-execed'
201271 @reexeced = true
202272 when '--proxy'
@@ -233,13 +303,19 @@ EOF
233303 end
234304 end
235305
306+ parse_args ( )
307+
308+ # Be helpful when 'help' was used but not '--help'
309+ if @type == 'help'
310+ usage
311+ exit ( 0 )
312+ end
313+
236314 if ( Process . uid != 0 )
237315 @log . error ( 'Must run as root to install packages' )
238316 exit ( 1 )
239317 end
240318
241- parse_args ( )
242-
243319 ########## Force running as Ruby 2.x or fail here ##########
244320 ruby_interpreter_path = check_ruby_version_and_symlink
245321 force_ruby2x ( ruby_interpreter_path )
@@ -252,13 +328,17 @@ EOF
252328 return exit_ok
253329 end
254330
255- def get_ec2_metadata_region
256- begin
257- return IMDSV2 . region
258- rescue => error
259- @log . warn ( "Could not get region from EC2 metadata service at '#{ error . message } '" )
260- return nil
331+ def get_ec2_metadata_property ( property )
332+ if IMDS . imds_supported?
333+ begin
334+ return IMDS . send ( property )
335+ rescue => error
336+ @log . warn ( "Could not get #{ property } from EC2 metadata service at '#{ error . message } '" )
337+ end
338+ else
339+ @log . warn ( "EC2 metadata service unavailable..." )
261340 end
341+ return nil
262342 end
263343
264344 def get_region
@@ -267,27 +347,36 @@ EOF
267347 return region if region
268348
269349 @log . info ( 'Checking EC2 metadata service for region information...' )
270- region = get_ec2_metadata_region
350+ region = get_ec2_metadata_property ( :region )
271351 return region if region
272352
273353 @log . info ( 'Using fail-safe default region: us-east-1' )
274354 return 'us-east-1'
275355 end
276356
277- def get_s3_uri ( region , bucket , key )
278- if ( region == 'us-east-1' )
279- URI . parse ( "https://#{ bucket } .s3.amazonaws.com/#{ key } " )
280- elsif ( region . split ( "-" ) [ 0 ] == 'cn' )
281- URI . parse ( "https://#{ bucket } .s3.#{ region } .amazonaws.com.cn/#{ key } " )
282- else
283- URI . parse ( "https://#{ bucket } .s3.#{ region } .amazonaws.com/#{ key } " )
357+ def get_domain ( fallback_region = nil )
358+ @log . info ( 'Checking AWS_DOMAIN environment variable for domain information...' )
359+ domain = ENV [ 'AWS_DOMAIN' ]
360+ return domain if domain
361+
362+ @log . info ( 'Checking EC2 metadata service for domain information...' )
363+ domain = get_ec2_metadata_property ( :domain )
364+ return domain if domain
365+
366+ domain = 'amazonaws.com'
367+ if !fallback_region . nil? && fallback_region . split ( "-" ) [ 0 ] == 'cn'
368+ domain = 'amazonaws.com.cn'
284369 end
370+
371+ @log . info ( "Using fail-safe default domain: #{ domain } " )
372+ return domain
285373 end
286374
287- def get_package_from_s3 ( region , bucket , key , package_file )
288- @log . info ( "Downloading package from bucket #{ bucket } and key #{ key } ..." )
375+ def get_package_from_s3 ( s3_bucket , key , package_file )
376+ @log . info ( "Downloading package from bucket #{ s3_bucket . bucket } and key #{ key } ..." )
289377
290- uri = get_s3_uri ( region , bucket , key )
378+ uri = s3_bucket . object_uri ( key )
379+ @log . info ( "Endpoint: #{ uri } " )
291380
292381 # stream package file to disk
293382 retries ||= 0
@@ -309,10 +398,11 @@ EOF
309398 end
310399end
311400
312- def get_version_file_from_s3 ( region , bucket , key )
313- @log . info ( "Downloading version file from bucket #{ bucket } and key #{ key } ..." )
401+ def get_version_file_from_s3 ( s3_bucket , key )
402+ @log . info ( "Downloading version file from bucket #{ s3_bucket . bucket } and key #{ key } ..." )
314403
315- uri = get_s3_uri ( region , bucket , key )
404+ uri = s3_bucket . object_uri ( key )
405+ @log . info ( "Endpoint: #{ uri } " )
316406
317407 begin
318408 require 'json'
@@ -325,13 +415,13 @@ end
325415 end
326416 end
327417
328- def install_from_s3 ( region , bucket , package_key , install_cmd )
418+ def install_from_s3 ( s3_bucket , package_key , install_cmd )
329419 package_base_name = File . basename ( package_key )
330420 package_extension = File . extname ( package_base_name )
331421 package_name = File . basename ( package_base_name , package_extension )
332422 package_file = Tempfile . new ( [ "#{ package_name } .tmp-" , package_extension ] ) # unique file with 0600 permissions
333423
334- get_package_from_s3 ( region , bucket , package_key , package_file )
424+ get_package_from_s3 ( s3_bucket , package_key , package_file )
335425 package_file . close
336426
337427 install_cmd << package_file . path
@@ -358,10 +448,10 @@ end
358448 end
359449 end
360450
361- def get_target_version ( target_version , type , region , bucket )
451+ def get_target_version ( target_version , type , s3_bucket )
362452 if target_version . nil?
363453 version_file_key = 'latest/LATEST_VERSION'
364- version_data = get_version_file_from_s3 ( region , bucket , version_file_key )
454+ version_data = get_version_file_from_s3 ( s3_bucket , version_file_key )
365455 if version_data . include? type
366456 return version_data [ type ]
367457 else
@@ -408,14 +498,14 @@ end
408498 end
409499 end
410500
411- region = get_region
501+ region = get_region ( )
502+ domain = get_domain ( region )
412503 bucket = "aws-codedeploy-#{ region } "
504+ s3_bucket = S3Bucket . new ( domain , region , bucket )
413505
414- target_version = get_target_version ( @target_version_arg , @type , region , bucket )
506+ target_version = get_target_version ( @target_version_arg , @type , s3_bucket )
415507
416508 case @type
417- when 'help'
418- usage
419509 when 'rpm'
420510 running_version = `rpm -q codedeploy-agent`
421511 running_version . strip!
424514 else
425515 #use -y to answer yes to confirmation prompts
426516 install_cmd = [ '/usr/bin/yum' , '-y' , 'localinstall' ]
427- install_from_s3 ( region , bucket , target_version , install_cmd )
517+ install_from_s3 ( s3_bucket , target_version , install_cmd )
428518 do_sanity_check ( '/sbin/service' )
429519 end
430520 when 'deb'
@@ -443,13 +533,13 @@ end
443533 #use -n for non-interactive mode
444534 #use -o to not overwrite config files unless they have not been changed
445535 install_cmd = [ '/usr/bin/gdebi' , '-n' , '-o' , 'Dpkg::Options::=--force-confdef' , '-o' , 'Dkpg::Options::=--force-confold' ]
446- install_from_s3 ( region , bucket , target_version , install_cmd )
536+ install_from_s3 ( s3_bucket , target_version , install_cmd )
447537 do_sanity_check ( '/usr/sbin/service' )
448538 end
449539 when 'zypper'
450540 #use -n for non-interactive mode
451541 install_cmd = [ '/usr/bin/zypper' , 'install' , '-n' ]
452- install_from_s3 ( region , bucket , target_version , install_cmd )
542+ install_from_s3 ( s3_bucket , target_version , install_cmd )
453543 else
454544 @log . error ( "Unsupported package type '#{ @type } '" )
455545 exit ( 1 )
0 commit comments