5
5
# than 2.0. Testing on multiple Ruby versions is required for
6
6
# changes to this part of the code.
7
7
##################################################################
8
- require 'json'
9
-
10
8
class Proxy
11
9
instance_methods . each do |m |
12
10
undef_method m unless m =~ /(^__|^send$|^object_id$)/
43
41
@log . level = Logger ::INFO
44
42
45
43
require 'net/http'
46
- require 'json'
47
44
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
50
81
51
- class IMDSV2
52
82
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 )
54
102
end
55
103
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
+
56
115
private
57
116
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 |
59
118
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
61
124
raise "HTTP error from metadata service: #{ response . message } , code #{ response . code } "
62
125
end
63
- return response . body
64
126
end
65
127
end
66
128
67
- def self . put_request ( path )
129
+ def self . put_request ( path , & block )
68
130
request = Net ::HTTP ::Put . new ( path )
69
131
request [ 'X-aws-ec2-metadata-token-ttl-seconds' ] = '21600'
70
- http_request ( request )
132
+ http_request ( request , & block )
71
133
end
72
134
73
- def self . get_request ( path , token = nil )
135
+ def self . get_request ( path , token = nil , & block )
74
136
request = Net ::HTTP ::Get . new ( path )
75
137
unless token . nil?
76
138
request [ 'X-aws-ec2-metadata-token' ] = token
77
139
end
78
- http_request ( request )
140
+ http_request ( request , & block )
79
141
end
142
+ end
80
143
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 } " )
88
157
end
89
158
end
90
159
197
266
@sanity_check = true
198
267
when '--help'
199
268
usage
269
+ exit ( 0 )
200
270
when '--re-execed'
201
271
@reexeced = true
202
272
when '--proxy'
@@ -233,13 +303,19 @@ EOF
233
303
end
234
304
end
235
305
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
+
236
314
if ( Process . uid != 0 )
237
315
@log . error ( 'Must run as root to install packages' )
238
316
exit ( 1 )
239
317
end
240
318
241
- parse_args ( )
242
-
243
319
########## Force running as Ruby 2.x or fail here ##########
244
320
ruby_interpreter_path = check_ruby_version_and_symlink
245
321
force_ruby2x ( ruby_interpreter_path )
@@ -252,13 +328,17 @@ EOF
252
328
return exit_ok
253
329
end
254
330
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..." )
261
340
end
341
+ return nil
262
342
end
263
343
264
344
def get_region
@@ -267,27 +347,36 @@ EOF
267
347
return region if region
268
348
269
349
@log . info ( 'Checking EC2 metadata service for region information...' )
270
- region = get_ec2_metadata_region
350
+ region = get_ec2_metadata_property ( :region )
271
351
return region if region
272
352
273
353
@log . info ( 'Using fail-safe default region: us-east-1' )
274
354
return 'us-east-1'
275
355
end
276
356
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'
284
369
end
370
+
371
+ @log . info ( "Using fail-safe default domain: #{ domain } " )
372
+ return domain
285
373
end
286
374
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 } ..." )
289
377
290
- uri = get_s3_uri ( region , bucket , key )
378
+ uri = s3_bucket . object_uri ( key )
379
+ @log . info ( "Endpoint: #{ uri } " )
291
380
292
381
# stream package file to disk
293
382
retries ||= 0
@@ -309,10 +398,11 @@ EOF
309
398
end
310
399
end
311
400
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 } ..." )
314
403
315
- uri = get_s3_uri ( region , bucket , key )
404
+ uri = s3_bucket . object_uri ( key )
405
+ @log . info ( "Endpoint: #{ uri } " )
316
406
317
407
begin
318
408
require 'json'
@@ -325,13 +415,13 @@ end
325
415
end
326
416
end
327
417
328
- def install_from_s3 ( region , bucket , package_key , install_cmd )
418
+ def install_from_s3 ( s3_bucket , package_key , install_cmd )
329
419
package_base_name = File . basename ( package_key )
330
420
package_extension = File . extname ( package_base_name )
331
421
package_name = File . basename ( package_base_name , package_extension )
332
422
package_file = Tempfile . new ( [ "#{ package_name } .tmp-" , package_extension ] ) # unique file with 0600 permissions
333
423
334
- get_package_from_s3 ( region , bucket , package_key , package_file )
424
+ get_package_from_s3 ( s3_bucket , package_key , package_file )
335
425
package_file . close
336
426
337
427
install_cmd << package_file . path
@@ -358,10 +448,10 @@ end
358
448
end
359
449
end
360
450
361
- def get_target_version ( target_version , type , region , bucket )
451
+ def get_target_version ( target_version , type , s3_bucket )
362
452
if target_version . nil?
363
453
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 )
365
455
if version_data . include? type
366
456
return version_data [ type ]
367
457
else
@@ -408,14 +498,14 @@ end
408
498
end
409
499
end
410
500
411
- region = get_region
501
+ region = get_region ( )
502
+ domain = get_domain ( region )
412
503
bucket = "aws-codedeploy-#{ region } "
504
+ s3_bucket = S3Bucket . new ( domain , region , bucket )
413
505
414
- target_version = get_target_version ( @target_version_arg , @type , region , bucket )
506
+ target_version = get_target_version ( @target_version_arg , @type , s3_bucket )
415
507
416
508
case @type
417
- when 'help'
418
- usage
419
509
when 'rpm'
420
510
running_version = `rpm -q codedeploy-agent`
421
511
running_version . strip!
424
514
else
425
515
#use -y to answer yes to confirmation prompts
426
516
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 )
428
518
do_sanity_check ( '/sbin/service' )
429
519
end
430
520
when 'deb'
@@ -443,13 +533,13 @@ end
443
533
#use -n for non-interactive mode
444
534
#use -o to not overwrite config files unless they have not been changed
445
535
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 )
447
537
do_sanity_check ( '/usr/sbin/service' )
448
538
end
449
539
when 'zypper'
450
540
#use -n for non-interactive mode
451
541
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 )
453
543
else
454
544
@log . error ( "Unsupported package type '#{ @type } '" )
455
545
exit ( 1 )
0 commit comments