Skip to content

Commit 030bc3a

Browse files
authored
Merge pull request #271 from dljvette/v1.2.1
Release v1.2.1
2 parents a0e45b6 + 886e895 commit 030bc3a

File tree

25 files changed

+2684
-83
lines changed

25 files changed

+2684
-83
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ codedeploy-local.*.log
1010
deployment/
1111
.idea/
1212
.DS_STORE
13+
*.iml

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ group :test do
1616
gem 'fakefs', :require => 'fakefs/safe'
1717
gem 'mocha'
1818
gem 'rspec'
19+
gem 'webmock', :require => 'webmock/rspec'
1920
gem 'shoulda'
2021
gem 'shoulda-matchers'
2122
gem 'shoulda-context'

bin/install

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,52 @@ end
4242

4343
@log.level = Logger::INFO
4444

45+
require 'net/http'
46+
require 'json'
47+
48+
TOKEN_PATH = '/latest/api/token'
49+
DOCUMENT_PATH = '/latest/dynamic/instance-identity/document'
50+
51+
class IMDSV2
52+
def self.region
53+
doc['region'].strip
54+
end
55+
56+
private
57+
def self.http_request(request)
58+
Net::HTTP.start('169.254.169.254', 80, :read_timeout => 120, :open_timeout => 120) do |http|
59+
response = http.request(request)
60+
if response.code.to_i != 200
61+
raise "HTTP error from metadata service: #{response.message}, code #{response.code}"
62+
end
63+
return response.body
64+
end
65+
end
66+
67+
def self.put_request(path)
68+
request = Net::HTTP::Put.new(path)
69+
request['X-aws-ec2-metadata-token-ttl-seconds'] = '21600'
70+
http_request(request)
71+
end
72+
73+
def self.get_request(path, token = nil)
74+
request = Net::HTTP::Get.new(path)
75+
unless token.nil?
76+
request['X-aws-ec2-metadata-token'] = token
77+
end
78+
http_request(request)
79+
end
80+
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
88+
end
89+
end
90+
4591
begin
4692
require 'fileutils'
4793
require 'openssl'
@@ -208,12 +254,9 @@ EOF
208254

209255
def get_ec2_metadata_region
210256
begin
211-
uri = URI.parse('http://169.254.169.254/latest/dynamic/instance-identity/document')
212-
document_string = uri.read(:read_timeout => 120)
213-
doc = JSON.parse(document_string.strip)
214-
return doc['region'].strip
215-
rescue
216-
@log.warn("Could not get region from EC2 metadata service at '#{uri.to_s}'")
257+
return IMDSV2.region
258+
rescue => error
259+
@log.warn("Could not get region from EC2 metadata service at '#{error.message}'")
217260
return nil
218261
end
219262
end

bin/update

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,52 @@ end
4343

4444
@log.level = Logger::INFO
4545

46+
require 'net/http'
47+
require 'json'
48+
49+
TOKEN_PATH = '/latest/api/token'
50+
DOCUMENT_PATH = '/latest/dynamic/instance-identity/document'
51+
52+
class IMDSV2
53+
def self.region
54+
doc['region'].strip
55+
end
56+
57+
private
58+
def self.http_request(request)
59+
Net::HTTP.start('169.254.169.254', 80, :read_timeout => 120, :open_timeout => 120) do |http|
60+
response = http.request(request)
61+
if response.code.to_i != 200
62+
raise "HTTP error from metadata service: #{response.message}, code #{response.code}"
63+
end
64+
return response.body
65+
end
66+
end
67+
68+
def self.put_request(path)
69+
request = Net::HTTP::Put.new(path)
70+
request['X-aws-ec2-metadata-token-ttl-seconds'] = '21600'
71+
http_request(request)
72+
end
73+
74+
def self.get_request(path, token = nil)
75+
request = Net::HTTP::Get.new(path)
76+
unless token.nil?
77+
request['X-aws-ec2-metadata-token'] = token
78+
end
79+
http_request(request)
80+
end
81+
82+
def self.doc
83+
begin
84+
token = put_request(TOKEN_PATH)
85+
JSON.parse(get_request(DOCUMENT_PATH, token).strip)
86+
rescue
87+
JSON.parse(get_request(DOCUMENT_PATH).strip)
88+
end
89+
end
90+
end
91+
4692
require 'set'
4793
VALID_TYPES = Set.new ['rpm','zypper','deb','msi']
4894

@@ -275,12 +321,9 @@ EOF
275321

276322
def get_ec2_metadata_region
277323
begin
278-
uri = URI.parse('http://169.254.169.254/latest/dynamic/instance-identity/document')
279-
document_string = uri.read(:read_timeout => 120)
280-
doc = JSON.parse(document_string.strip)
281-
return doc['region'].strip
282-
rescue
283-
@log.warn("Could not get region from EC2 metadata service at '#{uri.to_s}'")
324+
return IMDSV2.region
325+
rescue => error
326+
@log.warn("Could not get region from EC2 metadata service at '#{error.message}'")
284327
return nil
285328
end
286329
end

codedeploy_agent.gemspec

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Gem::Specification.new do |spec|
22
spec.name = 'aws_codedeploy_agent'
3-
spec.version = '1.1.2'
3+
spec.version = '1.2.1'
44
spec.summary = 'Packages AWS CodeDeploy agent libraries'
55
spec.description = 'AWS CodeDeploy agent is responsible for doing the actual work of deploying software on an individual EC2 instance'
66
spec.author = 'Amazon Web Services'
@@ -14,13 +14,15 @@ Gem::Specification.new do |spec|
1414
spec.add_dependency('gli', '~> 2.5')
1515
spec.add_dependency('json_pure', '~> 1.6')
1616
spec.add_dependency('archive-tar-minitar', '~> 0.5.2')
17-
spec.add_dependency('rubyzip', '~> 1.1.0')
17+
spec.add_dependency('rubyzip', '~> 1.3.0')
1818
spec.add_dependency('logging', '~> 1.8')
19-
spec.add_dependency('aws-sdk-core', '~> 2.9')
19+
spec.add_dependency('aws-sdk-core', '~> 3')
20+
spec.add_dependency('aws-sdk-code-generator', '~> 0.2.2.pre')
21+
spec.add_dependency('aws-sdk-s3', '~> 1')
2022
spec.add_dependency('simple_pid', '~> 0.2.1')
2123
spec.add_dependency('docopt', '~> 0.5.0')
2224
spec.add_dependency('concurrent-ruby', '~> 1.0.5')
2325

24-
spec.add_development_dependency('rake', '~> 10.0')
26+
spec.add_development_dependency('rake', '~> 12.3.3')
2527
spec.add_development_dependency('rspec', '~> 3.2.0')
2628
end

features/step_definitions/common_steps.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'aws-sdk-core'
2+
require 'aws-sdk-s3'
23

34
$:.unshift File.join(File.dirname(File.expand_path('../..', __FILE__)), 'features')
45
require 'step_definitions/step_constants'

lib/instance_agent/file_credentials.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ def initialize(path)
1414
private
1515

1616
def refresh
17-
@credentials = Aws::SharedCredentials.new(path: @path)
17+
@credentials = Aws::SharedCredentials.new(path: @path).credentials
18+
raise "Failed to load credentials from path #{@path}" if @credentials.nil?
1819
@expiration = Time.new + 1800
1920
end
2021
end

lib/instance_agent/plugins/codedeploy/command_executor.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'openssl'
22
require 'fileutils'
33
require 'aws-sdk-core'
4+
require 'aws-sdk-s3'
45
require 'zlib'
56
require 'zip'
67
require 'instance_metadata'
@@ -12,6 +13,7 @@
1213
require 'instance_agent/plugins/codedeploy/deployment_specification'
1314
require 'instance_agent/plugins/codedeploy/hook_executor'
1415
require 'instance_agent/plugins/codedeploy/installer'
16+
require 'instance_agent/string_utils'
1517

1618
module InstanceAgent
1719
module Plugins
@@ -47,8 +49,8 @@ def initialize(options = {})
4749

4850
def self.command(name, &blk)
4951
@command_methods ||= Hash.new
50-
51-
method = Seahorse::Util.underscore(name).to_sym
52+
raise "Received command is not in PascalCase form: #{name.to_s}" unless StringUtils.is_pascal_case(name.to_s)
53+
method = StringUtils.underscore(name.to_s)
5254
@command_methods[name] = method
5355

5456
define_method(method, &blk)

lib/instance_agent/string_utils.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module InstanceAgent
2+
class StringUtils
3+
4+
def self.underscore(string)
5+
string.
6+
gsub(/([A-Z0-9]+)([A-Z][a-z])/, '\1_\2').
7+
scan(/[a-z0-9]+|\d+|[A-Z0-9]+[a-z]*/).
8+
join('_').downcase
9+
end
10+
11+
def self.is_pascal_case(string)
12+
!!(string =~ /^([A-Z][a-z0-9]+)+/)
13+
end
14+
15+
end
16+
end

lib/instance_metadata.rb

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,26 @@ class InstanceMetadata
99
PORT = 80
1010
HTTP_TIMEOUT = 30
1111

12+
PARTITION_PATH = '/latest/meta-data/services/partition'
13+
INSTANCE_ID_PATH = '/latest/meta-data/instance-id'
14+
TOKEN_PATH = '/latest/api/token'
15+
DOCUMENT_PATH = '/latest/dynamic/instance-identity/document'
16+
1217
def self.host_identifier
1318
"arn:#{partition}:ec2:#{doc['region']}:#{doc['accountId']}:instance/#{doc['instanceId']}"
1419
end
1520

1621
def self.partition
17-
http_get('/latest/meta-data/services/partition').strip
22+
get_metadata_wrapper(PARTITION_PATH).strip
1823
end
1924

2025
def self.region
21-
doc['region']
26+
doc['region'].strip
2227
end
2328

2429
def self.instance_id
2530
begin
26-
Net::HTTP.start(IP_ADDRESS, PORT) do |http|
27-
response = http.get('/latest/meta-data/instance-id')
28-
if response.code.to_i != 200
29-
return nil
30-
end
31-
return response.body
32-
end
31+
get_metadata_wrapper(INSTANCE_ID_PATH)
3332
rescue
3433
return nil
3534
end
@@ -39,19 +38,48 @@ class InstanceMetadataError < StandardError
3938
end
4039

4140
private
42-
def self.http_get(path)
43-
Net::HTTP.start(IP_ADDRESS, PORT, :read_timeout => HTTP_TIMEOUT/2, :open_timeout => HTTP_TIMEOUT/2) do |http|
44-
response = http.get(path)
41+
def self.get_metadata_wrapper(path)
42+
begin
43+
token = put_request(TOKEN_PATH)
44+
get_request(path, token)
45+
rescue
46+
InstanceAgent::Log.send(:info, "IMDSv2 http request failed, falling back to IMDSv1.")
47+
get_request(path)
48+
end
49+
50+
end
51+
52+
def self.http_request(request)
53+
Net::HTTP.start(IP_ADDRESS, PORT, :read_timeout => 120, :open_timeout => 120) do |http|
54+
response = http.request(request)
4555
if response.code.to_i != 200
46-
InstanceAgent::Log.send(:debug, "HTTP error from metadata service, code #{response.code}")
47-
raise "HTTP error from metadata service, code #{response.code}"
56+
raise "HTTP error from metadata service: #{response.message}, code #{response.code}"
4857
end
4958
return response.body
5059
end
5160
end
5261

53-
private
62+
def self.put_request(path)
63+
request = Net::HTTP::Put.new(path)
64+
request['X-aws-ec2-metadata-token-ttl-seconds'] = '21600'
65+
http_request(request)
66+
end
67+
68+
def self.get_request(path, token = nil)
69+
request = Net::HTTP::Get.new(path)
70+
unless token.nil?
71+
request['X-aws-ec2-metadata-token'] = token
72+
end
73+
http_request(request)
74+
end
75+
5476
def self.doc
55-
JSON.parse(http_get('/latest/dynamic/instance-identity/document').strip)
77+
begin
78+
token = put_request(TOKEN_PATH)
79+
JSON.parse(get_request(DOCUMENT_PATH, token).strip)
80+
rescue
81+
InstanceAgent::Log.send(:info, "IMDSv2 http request failed, falling back to IMDSv1.")
82+
JSON.parse(get_request(DOCUMENT_PATH).strip)
83+
end
5684
end
5785
end

spec/add_service_wrapper_spec.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# frozen_string_literal: true
2+
package_root = File.dirname(File.dirname(__FILE__))
3+
4+
require "#{package_root}/vendor/gems/codedeploy-commands-1.0.0/lib/aws/add_service_wrapper"
5+
6+
RSpec.describe 'add_service_wrapper' do
7+
8+
# This test is taken from the AwsSdkRubyCodeGenWrapper
9+
# https://code.amazon.com/packages/AwsSdkRubyCodeGenWrapper/blobs/mainline/--/spec/add_service_wrapper_spec.rb
10+
describe '#add_service' do
11+
before(:all) do
12+
@service_file = File.expand_path('../fixtures/sample_service.json', __FILE__)
13+
@api = JSON.parse(File.read(@service_file))
14+
@svc_class = Aws.add_service('GeneratedService', api: @api)
15+
end
16+
17+
let(:client) {Aws::GeneratedService::Client.new(stub_responses: true) }
18+
19+
it 'can create a valid client' do
20+
expect(client).to be_instance_of(Aws::GeneratedService::Client)
21+
end
22+
23+
it 'can create a client from the returned namespace' do
24+
expect(@svc_class::Client.new(stub_responses: true))
25+
.to be_instance_of(Aws::GeneratedService::Client)
26+
end
27+
28+
it 'can set constants on the returned namespace' do
29+
@svc_class.const_set(:VERSION, '1.1.42')
30+
expect(Aws::GeneratedService::VERSION).to eq('1.1.42')
31+
end
32+
33+
it 'can add plugins to the generated client' do
34+
class MyPlugin; end
35+
Aws::GeneratedService::Client.add_plugin(MyPlugin)
36+
expect(Aws::GeneratedService::Client.plugins).to include(MyPlugin)
37+
end
38+
39+
it 'can generate a whitelabel (non-Aws) service' do
40+
Aws.add_service('MyService', api: @api, whitelabel: true)
41+
expect(MyService::Client.new(stub_responses: true))
42+
.to be_instance_of(MyService::Client)
43+
end
44+
45+
it 'loads the model from a string path' do
46+
Aws.add_service('StringPathService', api: @service_file)
47+
expect(Aws::StringPathService::Client.new(stub_responses: true))
48+
.to be_instance_of(Aws::StringPathService::Client)
49+
end
50+
51+
it 'loads the model from a PathName' do
52+
Aws.add_service('PathService', api: Pathname.new(@service_file))
53+
expect(Aws::PathService::Client.new(stub_responses: true))
54+
.to be_instance_of(Aws::PathService::Client)
55+
end
56+
57+
it 'raises an ArgumentError if api is not provided' do
58+
expect do
59+
Aws.add_service('NoApiService')
60+
end.to raise_exception(ArgumentError)
61+
end
62+
end
63+
end

0 commit comments

Comments
 (0)