diff --git a/.gitignore b/.gitignore index 1ba9a25b..abe22a85 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ codedeploy-local.*.log deployment/ .idea/ .DS_STORE +*.iml diff --git a/Gemfile b/Gemfile index cabed145..0ae69d18 100644 --- a/Gemfile +++ b/Gemfile @@ -16,6 +16,7 @@ group :test do gem 'fakefs', :require => 'fakefs/safe' gem 'mocha' gem 'rspec' + gem 'webmock', :require => 'webmock/rspec' gem 'shoulda' gem 'shoulda-matchers' gem 'shoulda-context' diff --git a/bin/install b/bin/install index 948e8a70..76217349 100755 --- a/bin/install +++ b/bin/install @@ -42,6 +42,52 @@ end @log.level = Logger::INFO +require 'net/http' +require 'json' + +TOKEN_PATH = '/latest/api/token' +DOCUMENT_PATH = '/latest/dynamic/instance-identity/document' + +class IMDSV2 + def self.region + doc['region'].strip + end + + private + def self.http_request(request) + Net::HTTP.start('169.254.169.254', 80, :read_timeout => 120, :open_timeout => 120) do |http| + response = http.request(request) + if response.code.to_i != 200 + raise "HTTP error from metadata service: #{response.message}, code #{response.code}" + end + return response.body + end + end + + def self.put_request(path) + request = Net::HTTP::Put.new(path) + request['X-aws-ec2-metadata-token-ttl-seconds'] = '21600' + http_request(request) + end + + def self.get_request(path, token = nil) + request = Net::HTTP::Get.new(path) + unless token.nil? + request['X-aws-ec2-metadata-token'] = token + end + http_request(request) + 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 + end +end + begin require 'fileutils' require 'openssl' @@ -208,12 +254,9 @@ EOF def get_ec2_metadata_region begin - uri = URI.parse('http://169.254.169.254/latest/dynamic/instance-identity/document') - document_string = uri.read(:read_timeout => 120) - doc = JSON.parse(document_string.strip) - return doc['region'].strip - rescue - @log.warn("Could not get region from EC2 metadata service at '#{uri.to_s}'") + return IMDSV2.region + rescue => error + @log.warn("Could not get region from EC2 metadata service at '#{error.message}'") return nil end end diff --git a/bin/update b/bin/update index 33985bd2..b4919fed 100755 --- a/bin/update +++ b/bin/update @@ -43,6 +43,52 @@ end @log.level = Logger::INFO +require 'net/http' +require 'json' + +TOKEN_PATH = '/latest/api/token' +DOCUMENT_PATH = '/latest/dynamic/instance-identity/document' + +class IMDSV2 + def self.region + doc['region'].strip + end + + private + def self.http_request(request) + Net::HTTP.start('169.254.169.254', 80, :read_timeout => 120, :open_timeout => 120) do |http| + response = http.request(request) + if response.code.to_i != 200 + raise "HTTP error from metadata service: #{response.message}, code #{response.code}" + end + return response.body + end + end + + def self.put_request(path) + request = Net::HTTP::Put.new(path) + request['X-aws-ec2-metadata-token-ttl-seconds'] = '21600' + http_request(request) + end + + def self.get_request(path, token = nil) + request = Net::HTTP::Get.new(path) + unless token.nil? + request['X-aws-ec2-metadata-token'] = token + end + http_request(request) + 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 + end +end + require 'set' VALID_TYPES = Set.new ['rpm','zypper','deb','msi'] @@ -275,12 +321,9 @@ EOF def get_ec2_metadata_region begin - uri = URI.parse('http://169.254.169.254/latest/dynamic/instance-identity/document') - document_string = uri.read(:read_timeout => 120) - doc = JSON.parse(document_string.strip) - return doc['region'].strip - rescue - @log.warn("Could not get region from EC2 metadata service at '#{uri.to_s}'") + return IMDSV2.region + rescue => error + @log.warn("Could not get region from EC2 metadata service at '#{error.message}'") return nil end end diff --git a/codedeploy_agent.gemspec b/codedeploy_agent.gemspec index 409be663..f03ae183 100644 --- a/codedeploy_agent.gemspec +++ b/codedeploy_agent.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = 'aws_codedeploy_agent' - spec.version = '1.1.2' + spec.version = '1.2.1' spec.summary = 'Packages AWS CodeDeploy agent libraries' spec.description = 'AWS CodeDeploy agent is responsible for doing the actual work of deploying software on an individual EC2 instance' spec.author = 'Amazon Web Services' @@ -14,13 +14,15 @@ Gem::Specification.new do |spec| spec.add_dependency('gli', '~> 2.5') spec.add_dependency('json_pure', '~> 1.6') spec.add_dependency('archive-tar-minitar', '~> 0.5.2') - spec.add_dependency('rubyzip', '~> 1.1.0') + spec.add_dependency('rubyzip', '~> 1.3.0') spec.add_dependency('logging', '~> 1.8') - spec.add_dependency('aws-sdk-core', '~> 2.9') + spec.add_dependency('aws-sdk-core', '~> 3') + spec.add_dependency('aws-sdk-code-generator', '~> 0.2.2.pre') + spec.add_dependency('aws-sdk-s3', '~> 1') spec.add_dependency('simple_pid', '~> 0.2.1') spec.add_dependency('docopt', '~> 0.5.0') spec.add_dependency('concurrent-ruby', '~> 1.0.5') - spec.add_development_dependency('rake', '~> 10.0') + spec.add_development_dependency('rake', '~> 12.3.3') spec.add_development_dependency('rspec', '~> 3.2.0') end diff --git a/features/step_definitions/common_steps.rb b/features/step_definitions/common_steps.rb index 296d5a9a..97508a78 100644 --- a/features/step_definitions/common_steps.rb +++ b/features/step_definitions/common_steps.rb @@ -1,4 +1,5 @@ require 'aws-sdk-core' +require 'aws-sdk-s3' $:.unshift File.join(File.dirname(File.expand_path('../..', __FILE__)), 'features') require 'step_definitions/step_constants' diff --git a/lib/instance_agent/file_credentials.rb b/lib/instance_agent/file_credentials.rb index 18944bf7..2d5b800b 100644 --- a/lib/instance_agent/file_credentials.rb +++ b/lib/instance_agent/file_credentials.rb @@ -14,7 +14,8 @@ def initialize(path) private def refresh - @credentials = Aws::SharedCredentials.new(path: @path) + @credentials = Aws::SharedCredentials.new(path: @path).credentials + raise "Failed to load credentials from path #{@path}" if @credentials.nil? @expiration = Time.new + 1800 end end diff --git a/lib/instance_agent/plugins/codedeploy/command_executor.rb b/lib/instance_agent/plugins/codedeploy/command_executor.rb index e8d2e12b..99463247 100644 --- a/lib/instance_agent/plugins/codedeploy/command_executor.rb +++ b/lib/instance_agent/plugins/codedeploy/command_executor.rb @@ -1,6 +1,7 @@ require 'openssl' require 'fileutils' require 'aws-sdk-core' +require 'aws-sdk-s3' require 'zlib' require 'zip' require 'instance_metadata' @@ -12,6 +13,7 @@ require 'instance_agent/plugins/codedeploy/deployment_specification' require 'instance_agent/plugins/codedeploy/hook_executor' require 'instance_agent/plugins/codedeploy/installer' +require 'instance_agent/string_utils' module InstanceAgent module Plugins @@ -47,8 +49,8 @@ def initialize(options = {}) def self.command(name, &blk) @command_methods ||= Hash.new - - method = Seahorse::Util.underscore(name).to_sym + raise "Received command is not in PascalCase form: #{name.to_s}" unless StringUtils.is_pascal_case(name.to_s) + method = StringUtils.underscore(name.to_s) @command_methods[name] = method define_method(method, &blk) diff --git a/lib/instance_agent/string_utils.rb b/lib/instance_agent/string_utils.rb new file mode 100644 index 00000000..2746ff2d --- /dev/null +++ b/lib/instance_agent/string_utils.rb @@ -0,0 +1,16 @@ +module InstanceAgent + class StringUtils + + def self.underscore(string) + string. + gsub(/([A-Z0-9]+)([A-Z][a-z])/, '\1_\2'). + scan(/[a-z0-9]+|\d+|[A-Z0-9]+[a-z]*/). + join('_').downcase + end + + def self.is_pascal_case(string) + !!(string =~ /^([A-Z][a-z0-9]+)+/) + end + + end +end \ No newline at end of file diff --git a/lib/instance_metadata.rb b/lib/instance_metadata.rb index 4559aca0..6c9238cc 100644 --- a/lib/instance_metadata.rb +++ b/lib/instance_metadata.rb @@ -9,27 +9,26 @@ class InstanceMetadata PORT = 80 HTTP_TIMEOUT = 30 + PARTITION_PATH = '/latest/meta-data/services/partition' + INSTANCE_ID_PATH = '/latest/meta-data/instance-id' + TOKEN_PATH = '/latest/api/token' + DOCUMENT_PATH = '/latest/dynamic/instance-identity/document' + def self.host_identifier "arn:#{partition}:ec2:#{doc['region']}:#{doc['accountId']}:instance/#{doc['instanceId']}" end def self.partition - http_get('/latest/meta-data/services/partition').strip + get_metadata_wrapper(PARTITION_PATH).strip end def self.region - doc['region'] + doc['region'].strip end def self.instance_id begin - Net::HTTP.start(IP_ADDRESS, PORT) do |http| - response = http.get('/latest/meta-data/instance-id') - if response.code.to_i != 200 - return nil - end - return response.body - end + get_metadata_wrapper(INSTANCE_ID_PATH) rescue return nil end @@ -39,19 +38,48 @@ class InstanceMetadataError < StandardError end private - def self.http_get(path) - Net::HTTP.start(IP_ADDRESS, PORT, :read_timeout => HTTP_TIMEOUT/2, :open_timeout => HTTP_TIMEOUT/2) do |http| - response = http.get(path) + def self.get_metadata_wrapper(path) + begin + token = put_request(TOKEN_PATH) + get_request(path, token) + rescue + InstanceAgent::Log.send(:info, "IMDSv2 http request failed, falling back to IMDSv1.") + get_request(path) + end + + end + + def self.http_request(request) + Net::HTTP.start(IP_ADDRESS, PORT, :read_timeout => 120, :open_timeout => 120) do |http| + response = http.request(request) if response.code.to_i != 200 - InstanceAgent::Log.send(:debug, "HTTP error from metadata service, code #{response.code}") - raise "HTTP error from metadata service, code #{response.code}" + raise "HTTP error from metadata service: #{response.message}, code #{response.code}" end return response.body end end - private + def self.put_request(path) + request = Net::HTTP::Put.new(path) + request['X-aws-ec2-metadata-token-ttl-seconds'] = '21600' + http_request(request) + end + + def self.get_request(path, token = nil) + request = Net::HTTP::Get.new(path) + unless token.nil? + request['X-aws-ec2-metadata-token'] = token + end + http_request(request) + end + def self.doc - JSON.parse(http_get('/latest/dynamic/instance-identity/document').strip) + begin + token = put_request(TOKEN_PATH) + JSON.parse(get_request(DOCUMENT_PATH, token).strip) + rescue + InstanceAgent::Log.send(:info, "IMDSv2 http request failed, falling back to IMDSv1.") + JSON.parse(get_request(DOCUMENT_PATH).strip) + end end end diff --git a/spec/add_service_wrapper_spec.rb b/spec/add_service_wrapper_spec.rb new file mode 100644 index 00000000..cddb7ce1 --- /dev/null +++ b/spec/add_service_wrapper_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true +package_root = File.dirname(File.dirname(__FILE__)) + +require "#{package_root}/vendor/gems/codedeploy-commands-1.0.0/lib/aws/add_service_wrapper" + +RSpec.describe 'add_service_wrapper' do + + # This test is taken from the AwsSdkRubyCodeGenWrapper + # https://code.amazon.com/packages/AwsSdkRubyCodeGenWrapper/blobs/mainline/--/spec/add_service_wrapper_spec.rb + describe '#add_service' do + before(:all) do + @service_file = File.expand_path('../fixtures/sample_service.json', __FILE__) + @api = JSON.parse(File.read(@service_file)) + @svc_class = Aws.add_service('GeneratedService', api: @api) + end + + let(:client) {Aws::GeneratedService::Client.new(stub_responses: true) } + + it 'can create a valid client' do + expect(client).to be_instance_of(Aws::GeneratedService::Client) + end + + it 'can create a client from the returned namespace' do + expect(@svc_class::Client.new(stub_responses: true)) + .to be_instance_of(Aws::GeneratedService::Client) + end + + it 'can set constants on the returned namespace' do + @svc_class.const_set(:VERSION, '1.1.42') + expect(Aws::GeneratedService::VERSION).to eq('1.1.42') + end + + it 'can add plugins to the generated client' do + class MyPlugin; end + Aws::GeneratedService::Client.add_plugin(MyPlugin) + expect(Aws::GeneratedService::Client.plugins).to include(MyPlugin) + end + + it 'can generate a whitelabel (non-Aws) service' do + Aws.add_service('MyService', api: @api, whitelabel: true) + expect(MyService::Client.new(stub_responses: true)) + .to be_instance_of(MyService::Client) + end + + it 'loads the model from a string path' do + Aws.add_service('StringPathService', api: @service_file) + expect(Aws::StringPathService::Client.new(stub_responses: true)) + .to be_instance_of(Aws::StringPathService::Client) + end + + it 'loads the model from a PathName' do + Aws.add_service('PathService', api: Pathname.new(@service_file)) + expect(Aws::PathService::Client.new(stub_responses: true)) + .to be_instance_of(Aws::PathService::Client) + end + + it 'raises an ArgumentError if api is not provided' do + expect do + Aws.add_service('NoApiService') + end.to raise_exception(ArgumentError) + end + end +end \ No newline at end of file diff --git a/spec/aws/codedeploy/local/master_spec.rb b/spec/aws/codedeploy/local/master_spec.rb new file mode 100644 index 00000000..5630de1b --- /dev/null +++ b/spec/aws/codedeploy/local/master_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' +require_relative '../../../../vendor/gems/process_manager-0.0.13/lib/process_manager/master' +require 'fileutils' + +describe ProcessManager::Daemon::Master do + describe "check status" do + context "PID file is empty" do + it "status is nil and PID file is deleted" do + # Make directory + file_name = ProcessManager::Daemon::Master.pid_file + + dirname = File.dirname(file_name) + unless File.directory?(dirname) + FileUtils.mkdir_p(dirname) + end + + # Write empty file + out_file = File.new(file_name, "w") + out_file.close + + # Check that status is equal to nil and that the PID file is deleted + # Note: This used to give a status of 0 but we want it to be nil + expect(ProcessManager::Daemon::Master.status).to eq(nil) + expect(File.exist?(file_name)).to eq(false) + + # Clean up directory + FileUtils.remove_dir(dirname) if File.directory?(dirname) + end + end + + context "PID file has a process that is running" do + it "status is the PID number" do + # Make directory + file_name = ProcessManager::Daemon::Master.pid_file + + dirname = File.dirname(file_name) + unless File.directory?(dirname) + FileUtils.mkdir_p(dirname) + end + + # Write empty file + out_file = File.new(file_name, "w") + File.write(file_name, $$) # Using $$ to mock a running process + out_file.close + + expect(ProcessManager::Daemon::Master.status).to eq($$) + + # Clean up and delete the file and directory + File.delete(file_name) if File.exist?(file_name) + FileUtils.remove_dir(dirname) if File.directory?(dirname) + end + end + end +end \ No newline at end of file diff --git a/spec/fixtures/sample_service.json b/spec/fixtures/sample_service.json new file mode 100644 index 00000000..f9c7ea5d --- /dev/null +++ b/spec/fixtures/sample_service.json @@ -0,0 +1,2144 @@ +{ + "version":"2.0", + "metadata":{ + "apiVersion":"2012-06-01", + "checksumFormat":"sha256", + "endpointPrefix":"glacier", + "serviceFullName":"Amazon Glacier", + "signatureVersion":"v4", + "protocol":"rest-json" + }, + "operations":{ + "AbortMultipartUpload":{ + "name":"AbortMultipartUpload", + "http":{ + "method":"DELETE", + "requestUri":"/{accountId}/vaults/{vaultName}/multipart-uploads/{uploadId}", + "responseCode":204 + }, + "input":{"shape":"AbortMultipartUploadInput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "AbortVaultLock":{ + "name":"AbortVaultLock", + "http":{ + "method":"DELETE", + "requestUri":"/{accountId}/vaults/{vaultName}/lock-policy", + "responseCode":204 + }, + "input":{"shape":"AbortVaultLockInput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "AddTagsToVault":{ + "name":"AddTagsToVault", + "http":{ + "method":"POST", + "requestUri":"/{accountId}/vaults/{vaultName}/tags?operation=add", + "responseCode":204 + }, + "input":{"shape":"AddTagsToVaultInput"}, + "errors":[ + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"LimitExceededException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "CompleteMultipartUpload":{ + "name":"CompleteMultipartUpload", + "http":{ + "method":"POST", + "requestUri":"/{accountId}/vaults/{vaultName}/multipart-uploads/{uploadId}", + "responseCode":201 + }, + "input":{"shape":"CompleteMultipartUploadInput"}, + "output":{"shape":"ArchiveCreationOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "CompleteVaultLock":{ + "name":"CompleteVaultLock", + "http":{ + "method":"POST", + "requestUri":"/{accountId}/vaults/{vaultName}/lock-policy/{lockId}", + "responseCode":204 + }, + "input":{"shape":"CompleteVaultLockInput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "CreateVault":{ + "name":"CreateVault", + "http":{ + "method":"PUT", + "requestUri":"/{accountId}/vaults/{vaultName}", + "responseCode":201 + }, + "input":{"shape":"CreateVaultInput"}, + "output":{"shape":"CreateVaultOutput"}, + "errors":[ + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + }, + { + "shape":"LimitExceededException", + "error":{"httpStatusCode":400}, + "exception":true + } + ] + }, + "DeleteArchive":{ + "name":"DeleteArchive", + "http":{ + "method":"DELETE", + "requestUri":"/{accountId}/vaults/{vaultName}/archives/{archiveId}", + "responseCode":204 + }, + "input":{"shape":"DeleteArchiveInput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "DeleteVault":{ + "name":"DeleteVault", + "http":{ + "method":"DELETE", + "requestUri":"/{accountId}/vaults/{vaultName}", + "responseCode":204 + }, + "input":{"shape":"DeleteVaultInput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "DeleteVaultAccessPolicy":{ + "name":"DeleteVaultAccessPolicy", + "http":{ + "method":"DELETE", + "requestUri":"/{accountId}/vaults/{vaultName}/access-policy", + "responseCode":204 + }, + "input":{"shape":"DeleteVaultAccessPolicyInput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "DeleteVaultNotifications":{ + "name":"DeleteVaultNotifications", + "http":{ + "method":"DELETE", + "requestUri":"/{accountId}/vaults/{vaultName}/notification-configuration", + "responseCode":204 + }, + "input":{"shape":"DeleteVaultNotificationsInput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "DescribeJob":{ + "name":"DescribeJob", + "http":{ + "method":"GET", + "requestUri":"/{accountId}/vaults/{vaultName}/jobs/{jobId}" + }, + "input":{"shape":"DescribeJobInput"}, + "output":{"shape":"GlacierJobDescription"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "DescribeVault":{ + "name":"DescribeVault", + "http":{ + "method":"GET", + "requestUri":"/{accountId}/vaults/{vaultName}" + }, + "input":{"shape":"DescribeVaultInput"}, + "output":{"shape":"DescribeVaultOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "GetDataRetrievalPolicy":{ + "name":"GetDataRetrievalPolicy", + "http":{ + "method":"GET", + "requestUri":"/{accountId}/policies/data-retrieval" + }, + "input":{"shape":"GetDataRetrievalPolicyInput"}, + "output":{"shape":"GetDataRetrievalPolicyOutput"}, + "errors":[ + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "GetJobOutput":{ + "name":"GetJobOutput", + "http":{ + "method":"GET", + "requestUri":"/{accountId}/vaults/{vaultName}/jobs/{jobId}/output" + }, + "input":{"shape":"GetJobOutputInput"}, + "output":{"shape":"GetJobOutputOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "GetVaultAccessPolicy":{ + "name":"GetVaultAccessPolicy", + "http":{ + "method":"GET", + "requestUri":"/{accountId}/vaults/{vaultName}/access-policy" + }, + "input":{"shape":"GetVaultAccessPolicyInput"}, + "output":{"shape":"GetVaultAccessPolicyOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "GetVaultLock":{ + "name":"GetVaultLock", + "http":{ + "method":"GET", + "requestUri":"/{accountId}/vaults/{vaultName}/lock-policy" + }, + "input":{"shape":"GetVaultLockInput"}, + "output":{"shape":"GetVaultLockOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "GetVaultNotifications":{ + "name":"GetVaultNotifications", + "http":{ + "method":"GET", + "requestUri":"/{accountId}/vaults/{vaultName}/notification-configuration" + }, + "input":{"shape":"GetVaultNotificationsInput"}, + "output":{"shape":"GetVaultNotificationsOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "InitiateJob":{ + "name":"InitiateJob", + "http":{ + "method":"POST", + "requestUri":"/{accountId}/vaults/{vaultName}/jobs", + "responseCode":202 + }, + "input":{"shape":"InitiateJobInput"}, + "output":{"shape":"InitiateJobOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"PolicyEnforcedException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "InitiateMultipartUpload":{ + "name":"InitiateMultipartUpload", + "http":{ + "method":"POST", + "requestUri":"/{accountId}/vaults/{vaultName}/multipart-uploads", + "responseCode":201 + }, + "input":{"shape":"InitiateMultipartUploadInput"}, + "output":{"shape":"InitiateMultipartUploadOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "InitiateVaultLock":{ + "name":"InitiateVaultLock", + "http":{ + "method":"POST", + "requestUri":"/{accountId}/vaults/{vaultName}/lock-policy", + "responseCode":201 + }, + "input":{"shape":"InitiateVaultLockInput"}, + "output":{"shape":"InitiateVaultLockOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "ListJobs":{ + "name":"ListJobs", + "http":{ + "method":"GET", + "requestUri":"/{accountId}/vaults/{vaultName}/jobs" + }, + "input":{"shape":"ListJobsInput"}, + "output":{"shape":"ListJobsOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "ListMultipartUploads":{ + "name":"ListMultipartUploads", + "http":{ + "method":"GET", + "requestUri":"/{accountId}/vaults/{vaultName}/multipart-uploads" + }, + "input":{"shape":"ListMultipartUploadsInput"}, + "output":{"shape":"ListMultipartUploadsOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "ListParts":{ + "name":"ListParts", + "http":{ + "method":"GET", + "requestUri":"/{accountId}/vaults/{vaultName}/multipart-uploads/{uploadId}" + }, + "input":{"shape":"ListPartsInput"}, + "output":{"shape":"ListPartsOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "ListTagsForVault":{ + "name":"ListTagsForVault", + "http":{ + "method":"GET", + "requestUri":"/{accountId}/vaults/{vaultName}/tags" + }, + "input":{"shape":"ListTagsForVaultInput"}, + "output":{"shape":"ListTagsForVaultOutput"}, + "errors":[ + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "ListVaults":{ + "name":"ListVaults", + "http":{ + "method":"GET", + "requestUri":"/{accountId}/vaults" + }, + "input":{"shape":"ListVaultsInput"}, + "output":{"shape":"ListVaultsOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "RemoveTagsFromVault":{ + "name":"RemoveTagsFromVault", + "http":{ + "method":"POST", + "requestUri":"/{accountId}/vaults/{vaultName}/tags?operation=remove", + "responseCode":204 + }, + "input":{"shape":"RemoveTagsFromVaultInput"}, + "errors":[ + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "SetDataRetrievalPolicy":{ + "name":"SetDataRetrievalPolicy", + "http":{ + "method":"PUT", + "requestUri":"/{accountId}/policies/data-retrieval", + "responseCode":204 + }, + "input":{"shape":"SetDataRetrievalPolicyInput"}, + "errors":[ + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "SetVaultAccessPolicy":{ + "name":"SetVaultAccessPolicy", + "http":{ + "method":"PUT", + "requestUri":"/{accountId}/vaults/{vaultName}/access-policy", + "responseCode":204 + }, + "input":{"shape":"SetVaultAccessPolicyInput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "SetVaultNotifications":{ + "name":"SetVaultNotifications", + "http":{ + "method":"PUT", + "requestUri":"/{accountId}/vaults/{vaultName}/notification-configuration", + "responseCode":204 + }, + "input":{"shape":"SetVaultNotificationsInput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "UploadArchive":{ + "name":"UploadArchive", + "http":{ + "method":"POST", + "requestUri":"/{accountId}/vaults/{vaultName}/archives", + "responseCode":201 + }, + "input":{"shape":"UploadArchiveInput"}, + "output":{"shape":"ArchiveCreationOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"RequestTimeoutException", + "error":{"httpStatusCode":408}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + }, + "UploadMultipartPart":{ + "name":"UploadMultipartPart", + "http":{ + "method":"PUT", + "requestUri":"/{accountId}/vaults/{vaultName}/multipart-uploads/{uploadId}", + "responseCode":204 + }, + "input":{"shape":"UploadMultipartPartInput"}, + "output":{"shape":"UploadMultipartPartOutput"}, + "errors":[ + { + "shape":"ResourceNotFoundException", + "error":{"httpStatusCode":404}, + "exception":true + }, + { + "shape":"InvalidParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"MissingParameterValueException", + "error":{"httpStatusCode":400}, + "exception":true + }, + { + "shape":"RequestTimeoutException", + "error":{"httpStatusCode":408}, + "exception":true + }, + { + "shape":"ServiceUnavailableException", + "error":{"httpStatusCode":500}, + "exception":true + } + ] + } + }, + "shapes":{ + "AbortMultipartUploadInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "uploadId":{ + "shape":"string", + "location":"uri", + "locationName":"uploadId" + } + }, + "required":[ + "accountId", + "vaultName", + "uploadId" + ] + }, + "AbortVaultLockInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "ActionCode":{ + "type":"string", + "enum":[ + "ArchiveRetrieval", + "InventoryRetrieval" + ] + }, + "AddTagsToVaultInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "Tags":{"shape":"TagMap"} + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "ArchiveCreationOutput":{ + "type":"structure", + "members":{ + "location":{ + "shape":"string", + "location":"header", + "locationName":"Location" + }, + "checksum":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-sha256-tree-hash" + }, + "archiveId":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-archive-id" + } + } + }, + "CompleteMultipartUploadInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "uploadId":{ + "shape":"string", + "location":"uri", + "locationName":"uploadId" + }, + "archiveSize":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-archive-size" + }, + "checksum":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-sha256-tree-hash" + } + }, + "required":[ + "accountId", + "vaultName", + "uploadId" + ] + }, + "CompleteVaultLockInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "lockId":{ + "shape":"string", + "location":"uri", + "locationName":"lockId" + } + }, + "required":[ + "accountId", + "vaultName", + "lockId" + ] + }, + "CreateVaultInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "CreateVaultOutput":{ + "type":"structure", + "members":{ + "location":{ + "shape":"string", + "location":"header", + "locationName":"Location" + } + } + }, + "DataRetrievalPolicy":{ + "type":"structure", + "members":{ + "Rules":{"shape":"DataRetrievalRulesList"} + } + }, + "DataRetrievalRule":{ + "type":"structure", + "members":{ + "Strategy":{"shape":"string"}, + "BytesPerHour":{"shape":"NullableLong"} + } + }, + "DataRetrievalRulesList":{ + "type":"list", + "member":{"shape":"DataRetrievalRule"} + }, + "DateTime":{"type":"string"}, + "DeleteArchiveInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "archiveId":{ + "shape":"string", + "location":"uri", + "locationName":"archiveId" + } + }, + "required":[ + "accountId", + "vaultName", + "archiveId" + ] + }, + "DeleteVaultAccessPolicyInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "DeleteVaultInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "DeleteVaultNotificationsInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "DescribeJobInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "jobId":{ + "shape":"string", + "location":"uri", + "locationName":"jobId" + } + }, + "required":[ + "accountId", + "vaultName", + "jobId" + ] + }, + "DescribeVaultInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "DescribeVaultOutput":{ + "type":"structure", + "members":{ + "VaultARN":{"shape":"string"}, + "VaultName":{"shape":"string"}, + "CreationDate":{"shape":"string"}, + "LastInventoryDate":{"shape":"string"}, + "NumberOfArchives":{"shape":"long"}, + "SizeInBytes":{"shape":"long"} + } + }, + "GetDataRetrievalPolicyInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + } + }, + "required":["accountId"] + }, + "GetDataRetrievalPolicyOutput":{ + "type":"structure", + "members":{ + "Policy":{"shape":"DataRetrievalPolicy"} + } + }, + "GetJobOutputInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "jobId":{ + "shape":"string", + "location":"uri", + "locationName":"jobId" + }, + "range":{ + "shape":"string", + "location":"header", + "locationName":"Range" + } + }, + "required":[ + "accountId", + "vaultName", + "jobId" + ] + }, + "GetJobOutputOutput":{ + "type":"structure", + "members":{ + "body":{"shape":"Stream"}, + "checksum":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-sha256-tree-hash" + }, + "status":{ + "shape":"httpstatus", + "location":"statusCode" + }, + "contentRange":{ + "shape":"string", + "location":"header", + "locationName":"Content-Range" + }, + "acceptRanges":{ + "shape":"string", + "location":"header", + "locationName":"Accept-Ranges" + }, + "contentType":{ + "shape":"string", + "location":"header", + "locationName":"Content-Type" + }, + "archiveDescription":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-archive-description" + } + }, + "payload":"body" + }, + "GetVaultAccessPolicyInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "GetVaultAccessPolicyOutput":{ + "type":"structure", + "members":{ + "policy":{"shape":"VaultAccessPolicy"} + }, + "payload":"policy" + }, + "GetVaultLockInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "GetVaultLockOutput":{ + "type":"structure", + "members":{ + "Policy":{"shape":"string"}, + "State":{"shape":"string"}, + "ExpirationDate":{"shape":"string"}, + "CreationDate":{"shape":"string"} + } + }, + "GetVaultNotificationsInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "GetVaultNotificationsOutput":{ + "type":"structure", + "members":{ + "vaultNotificationConfig":{"shape":"VaultNotificationConfig"} + }, + "payload":"vaultNotificationConfig" + }, + "GlacierJobDescription":{ + "type":"structure", + "members":{ + "JobId":{"shape":"string"}, + "JobDescription":{"shape":"string"}, + "Action":{"shape":"ActionCode"}, + "ArchiveId":{"shape":"string"}, + "VaultARN":{"shape":"string"}, + "CreationDate":{"shape":"string"}, + "Completed":{"shape":"boolean"}, + "StatusCode":{"shape":"StatusCode"}, + "StatusMessage":{"shape":"string"}, + "ArchiveSizeInBytes":{"shape":"Size"}, + "InventorySizeInBytes":{"shape":"Size"}, + "SNSTopic":{"shape":"string"}, + "CompletionDate":{"shape":"string"}, + "SHA256TreeHash":{"shape":"string"}, + "ArchiveSHA256TreeHash":{"shape":"string"}, + "RetrievalByteRange":{"shape":"string"}, + "InventoryRetrievalParameters":{"shape":"InventoryRetrievalJobDescription"} + } + }, + "InitiateJobInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "jobParameters":{"shape":"JobParameters"} + }, + "required":[ + "accountId", + "vaultName" + ], + "payload":"jobParameters" + }, + "InitiateJobOutput":{ + "type":"structure", + "members":{ + "location":{ + "shape":"string", + "location":"header", + "locationName":"Location" + }, + "jobId":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-job-id" + } + } + }, + "InitiateMultipartUploadInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "archiveDescription":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-archive-description" + }, + "partSize":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-part-size" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "InitiateMultipartUploadOutput":{ + "type":"structure", + "members":{ + "location":{ + "shape":"string", + "location":"header", + "locationName":"Location" + }, + "uploadId":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-multipart-upload-id" + } + } + }, + "InitiateVaultLockInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "policy":{"shape":"VaultLockPolicy"} + }, + "required":[ + "accountId", + "vaultName" + ], + "payload":"policy" + }, + "InitiateVaultLockOutput":{ + "type":"structure", + "members":{ + "lockId":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-lock-id" + } + } + }, + "InvalidParameterValueException":{ + "type":"structure", + "members":{ + "type":{"shape":"string"}, + "code":{"shape":"string"}, + "message":{"shape":"string"} + }, + "error":{"httpStatusCode":400}, + "exception":true + }, + "InventoryRetrievalJobDescription":{ + "type":"structure", + "members":{ + "Format":{"shape":"string"}, + "StartDate":{"shape":"DateTime"}, + "EndDate":{"shape":"DateTime"}, + "Limit":{"shape":"string"}, + "Marker":{"shape":"string"} + } + }, + "InventoryRetrievalJobInput":{ + "type":"structure", + "members":{ + "StartDate":{"shape":"string"}, + "EndDate":{"shape":"string"}, + "Limit":{"shape":"string"}, + "Marker":{"shape":"string"} + } + }, + "JobList":{ + "type":"list", + "member":{"shape":"GlacierJobDescription"} + }, + "JobParameters":{ + "type":"structure", + "members":{ + "Format":{"shape":"string"}, + "Type":{"shape":"string"}, + "ArchiveId":{"shape":"string"}, + "Description":{"shape":"string"}, + "SNSTopic":{"shape":"string"}, + "RetrievalByteRange":{"shape":"string"}, + "InventoryRetrievalParameters":{"shape":"InventoryRetrievalJobInput"} + } + }, + "LimitExceededException":{ + "type":"structure", + "members":{ + "type":{"shape":"string"}, + "code":{"shape":"string"}, + "message":{"shape":"string"} + }, + "error":{"httpStatusCode":400}, + "exception":true + }, + "ListJobsInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "limit":{ + "shape":"string", + "location":"querystring", + "locationName":"limit" + }, + "marker":{ + "shape":"string", + "location":"querystring", + "locationName":"marker" + }, + "statuscode":{ + "shape":"string", + "location":"querystring", + "locationName":"statuscode" + }, + "completed":{ + "shape":"string", + "location":"querystring", + "locationName":"completed" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "ListJobsOutput":{ + "type":"structure", + "members":{ + "JobList":{"shape":"JobList"}, + "Marker":{"shape":"string"} + } + }, + "ListMultipartUploadsInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "marker":{ + "shape":"string", + "location":"querystring", + "locationName":"marker" + }, + "limit":{ + "shape":"string", + "location":"querystring", + "locationName":"limit" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "ListMultipartUploadsOutput":{ + "type":"structure", + "members":{ + "UploadsList":{"shape":"UploadsList"}, + "Marker":{"shape":"string"} + } + }, + "ListPartsInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "uploadId":{ + "shape":"string", + "location":"uri", + "locationName":"uploadId" + }, + "marker":{ + "shape":"string", + "location":"querystring", + "locationName":"marker" + }, + "limit":{ + "shape":"string", + "location":"querystring", + "locationName":"limit" + } + }, + "required":[ + "accountId", + "vaultName", + "uploadId" + ] + }, + "ListPartsOutput":{ + "type":"structure", + "members":{ + "MultipartUploadId":{"shape":"string"}, + "VaultARN":{"shape":"string"}, + "ArchiveDescription":{"shape":"string"}, + "PartSizeInBytes":{"shape":"long"}, + "CreationDate":{"shape":"string"}, + "Parts":{"shape":"PartList"}, + "Marker":{"shape":"string"} + } + }, + "ListTagsForVaultInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + } + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "ListTagsForVaultOutput":{ + "type":"structure", + "members":{ + "Tags":{"shape":"TagMap"} + } + }, + "ListVaultsInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "marker":{ + "shape":"string", + "location":"querystring", + "locationName":"marker" + }, + "limit":{ + "shape":"string", + "location":"querystring", + "locationName":"limit" + } + }, + "required":["accountId"] + }, + "ListVaultsOutput":{ + "type":"structure", + "members":{ + "VaultList":{"shape":"VaultList"}, + "Marker":{"shape":"string"} + } + }, + "MissingParameterValueException":{ + "type":"structure", + "members":{ + "type":{"shape":"string"}, + "code":{"shape":"string"}, + "message":{"shape":"string"} + }, + "error":{"httpStatusCode":400}, + "exception":true + }, + "NotificationEventList":{ + "type":"list", + "member":{"shape":"string"} + }, + "NullableLong":{"type":"long"}, + "PartList":{ + "type":"list", + "member":{"shape":"PartListElement"} + }, + "PartListElement":{ + "type":"structure", + "members":{ + "RangeInBytes":{"shape":"string"}, + "SHA256TreeHash":{"shape":"string"} + } + }, + "PolicyEnforcedException":{ + "type":"structure", + "members":{ + "type":{"shape":"string"}, + "code":{"shape":"string"}, + "message":{"shape":"string"} + }, + "error":{"httpStatusCode":400}, + "exception":true + }, + "RemoveTagsFromVaultInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "TagKeys":{"shape":"TagKeyList"} + }, + "required":[ + "accountId", + "vaultName" + ] + }, + "RequestTimeoutException":{ + "type":"structure", + "members":{ + "type":{"shape":"string"}, + "code":{"shape":"string"}, + "message":{"shape":"string"} + }, + "error":{"httpStatusCode":408}, + "exception":true + }, + "ResourceNotFoundException":{ + "type":"structure", + "members":{ + "type":{"shape":"string"}, + "code":{"shape":"string"}, + "message":{"shape":"string"} + }, + "error":{"httpStatusCode":404}, + "exception":true + }, + "ServiceUnavailableException":{ + "type":"structure", + "members":{ + "type":{"shape":"string"}, + "code":{"shape":"string"}, + "message":{"shape":"string"} + }, + "error":{"httpStatusCode":500}, + "exception":true + }, + "SetDataRetrievalPolicyInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "Policy":{"shape":"DataRetrievalPolicy"} + }, + "required":["accountId"] + }, + "SetVaultAccessPolicyInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "policy":{"shape":"VaultAccessPolicy"} + }, + "required":[ + "accountId", + "vaultName" + ], + "payload":"policy" + }, + "SetVaultNotificationsInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "vaultNotificationConfig":{"shape":"VaultNotificationConfig"} + }, + "required":[ + "accountId", + "vaultName" + ], + "payload":"vaultNotificationConfig" + }, + "Size":{"type":"long"}, + "StatusCode":{ + "type":"string", + "enum":[ + "InProgress", + "Succeeded", + "Failed" + ] + }, + "Stream":{ + "type":"blob", + "streaming":true + }, + "TagKey":{"type":"string"}, + "TagKeyList":{ + "type":"list", + "member":{"shape":"string"} + }, + "TagMap":{ + "type":"map", + "key":{"shape":"TagKey"}, + "value":{"shape":"TagValue"} + }, + "TagValue":{"type":"string"}, + "UploadArchiveInput":{ + "type":"structure", + "members":{ + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "archiveDescription":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-archive-description" + }, + "checksum":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-sha256-tree-hash" + }, + "body":{"shape":"Stream"} + }, + "required":[ + "vaultName", + "accountId" + ], + "payload":"body" + }, + "UploadListElement":{ + "type":"structure", + "members":{ + "MultipartUploadId":{"shape":"string"}, + "VaultARN":{"shape":"string"}, + "ArchiveDescription":{"shape":"string"}, + "PartSizeInBytes":{"shape":"long"}, + "CreationDate":{"shape":"string"} + } + }, + "UploadMultipartPartInput":{ + "type":"structure", + "members":{ + "accountId":{ + "shape":"string", + "location":"uri", + "locationName":"accountId" + }, + "vaultName":{ + "shape":"string", + "location":"uri", + "locationName":"vaultName" + }, + "uploadId":{ + "shape":"string", + "location":"uri", + "locationName":"uploadId" + }, + "checksum":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-sha256-tree-hash" + }, + "range":{ + "shape":"string", + "location":"header", + "locationName":"Content-Range" + }, + "body":{"shape":"Stream"} + }, + "required":[ + "accountId", + "vaultName", + "uploadId" + ], + "payload":"body" + }, + "UploadMultipartPartOutput":{ + "type":"structure", + "members":{ + "checksum":{ + "shape":"string", + "location":"header", + "locationName":"x-amz-sha256-tree-hash" + } + } + }, + "UploadsList":{ + "type":"list", + "member":{"shape":"UploadListElement"} + }, + "VaultAccessPolicy":{ + "type":"structure", + "members":{ + "Policy":{"shape":"string"} + } + }, + "VaultList":{ + "type":"list", + "member":{"shape":"DescribeVaultOutput"} + }, + "VaultLockPolicy":{ + "type":"structure", + "members":{ + "Policy":{"shape":"string"} + } + }, + "VaultNotificationConfig":{ + "type":"structure", + "members":{ + "SNSTopic":{"shape":"string"}, + "Events":{"shape":"NotificationEventList"} + } + }, + "boolean":{"type":"boolean"}, + "httpstatus":{"type":"integer"}, + "long":{"type":"long"}, + "string":{"type":"string"} + } +} \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 21e9156b..c779112c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,7 @@ # Encoding: UTF-8 # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration require 'bundler/setup' +require 'webmock/rspec' Bundler.setup RSpec.configure do |config| diff --git a/test/instance_agent/file_credentials_test.rb b/test/instance_agent/file_credentials_test.rb index 634fd072..a25981bf 100644 --- a/test/instance_agent/file_credentials_test.rb +++ b/test/instance_agent/file_credentials_test.rb @@ -1,19 +1,60 @@ require 'test_helper' class FileCredentialsTest < InstanceAgentTestCase - context 'The file credentials' do - should 'pass the path to SharedCredentials' do - credentials = InstanceAgent::FileCredentials.new("/tmp/credentials_path") - Aws::SharedCredentials.expects(:new).with(path: "/tmp/credentials_path") + context 'With the file credentials' do + + access_key_id = "fake-aws-access-key-id" + secret_access_key = "fake-aws-secret-key" + credentials_path = "/tmp/credentials_path" + session_token_1 = "fake-aws-session-token-1" + session_token_2 = "fake-aws-session-token-2" + credential_file_pattern = <<-END +[default] +aws_access_key_id = #{access_key_id} +aws_secret_access_key = #{secret_access_key} +aws_session_token = %s +END + + setup do + File.stubs(:exist?).with(credentials_path).returns(true) + File.stubs(:exist?).with(Not(equals(credentials_path))).returns(false) + File.stubs(:readable?).with(credentials_path).returns(true) + File.expects(:read).with(credentials_path).returns(credential_file_pattern % session_token_2) + File.expects(:read).with(credentials_path).returns(credential_file_pattern % session_token_1) + end + + should 'load and refresh the credentials from the path to SharedCredentials' do + credentials = InstanceAgent::FileCredentials.new(credentials_path) + assert_equal access_key_id, credentials.credentials.access_key_id + assert_equal secret_access_key, credentials.credentials.secret_access_key + assert_equal session_token_1, credentials.credentials.session_token credentials.refresh! + assert_equal access_key_id, credentials.credentials.access_key_id + assert_equal secret_access_key, credentials.credentials.secret_access_key + assert_equal session_token_2, credentials.credentials.session_token end should 'set the refresh time to 30 minutes' do - credentials = InstanceAgent::FileCredentials.new("/tmp/credentials_path") + credentials = InstanceAgent::FileCredentials.new(credentials_path) credentials.refresh! # Around 30 minutes expected_time = Time.now + 1800 assert_in_delta(expected_time, credentials.expiration, 5, "Expiration time did not fall within 5 seconds of expected expiration") end end + + context 'Without the file credentials' do + + credentials_path = "/tmp/invalid_credentials_path" + + setup do + File.stubs(:exist?).with(credentials_path).returns(false) + end + + should 'raise error when credential file is missing' do + assert_raised_with_message("Failed to load credentials from path #{credentials_path}", RuntimeError) do + InstanceAgent::FileCredentials.new(credentials_path) + end + end + end end diff --git a/test/instance_agent/plugins/codedeploy/command_executor_test.rb b/test/instance_agent/plugins/codedeploy/command_executor_test.rb index f6be14e1..46635e03 100644 --- a/test/instance_agent/plugins/codedeploy/command_executor_test.rb +++ b/test/instance_agent/plugins/codedeploy/command_executor_test.rb @@ -1,7 +1,7 @@ require 'test_helper' require 'certificate_helper' require 'stringio' -require 'aws-sdk-core/s3' +require 'aws-sdk-s3' require 'aws/codedeploy/local/deployer' @@ -726,8 +726,8 @@ def generate_signed_message_for(map) #non 1:1 mapping tests context "one command hooks to multiple lifecycle events" do setup do - @command.command_name = "test_command" - @test_hook_mapping = { "test_command" => ["lifecycle_event_1","lifecycle_event_2"]} + @command.command_name = "TestCommand" + @test_hook_mapping = { "TestCommand" => ["lifecycle_event_1","lifecycle_event_2"]} @deploy_control_client = mock @command_executor = InstanceAgent::Plugins::CodeDeployPlugin::CommandExecutor.new({ :deploy_control_client => @deploy_control_client, diff --git a/test/instance_agent/plugins/codedeploy/onpremise_config_test.rb b/test/instance_agent/plugins/codedeploy/onpremise_config_test.rb index e5f9ab1d..df138eb5 100644 --- a/test/instance_agent/plugins/codedeploy/onpremise_config_test.rb +++ b/test/instance_agent/plugins/codedeploy/onpremise_config_test.rb @@ -73,14 +73,25 @@ class OnPremiseConfigTest < InstanceAgentTestCase end context "config file with session configuration" do + credentials_path = "/etc/codedeploy-agent/conf/.aws_credentials" linux_file = <<-END region: us-east-test iam_session_arn: test:arn - aws_credentials_file: /etc/codedeploy-agent/conf/.aws_credentials + aws_credentials_file: #{credentials_path} END + access_key_id = "fake-access-key-id-#{rand 1000}" + credentials_file = <<-END +[default] +aws_access_key_id = #{access_key_id} +aws_secret_access_key = fake-secret-access-key +aws_session_token = fake-session-token +END setup do File.stubs(:read).with(linux_path).returns(linux_file) + File.stubs(:read).with(credentials_path).returns(credentials_file) + File.stubs(:exist?).with(credentials_path).returns(true) + File.stubs(:readable?).with(credentials_path).returns(true) end should "set the ENV variables correctly" do @@ -88,6 +99,7 @@ class OnPremiseConfigTest < InstanceAgentTestCase assert_equal 'us-east-test', ENV['AWS_REGION'] assert_equal 'test:arn', ENV['AWS_HOST_IDENTIFIER'] assert_equal '/etc/codedeploy-agent/conf/.aws_credentials', ENV['AWS_CREDENTIALS_FILE'] + assert_equal access_key_id, Aws.config[:credentials].credentials.access_key_id end end diff --git a/test/instance_agent/plugins/codedeploy/version_tracking_test.rb b/test/instance_agent/plugins/codedeploy/version_tracking_test.rb index 84429de5..07983a1b 100644 --- a/test/instance_agent/plugins/codedeploy/version_tracking_test.rb +++ b/test/instance_agent/plugins/codedeploy/version_tracking_test.rb @@ -1,7 +1,7 @@ require 'test_helper' require 'certificate_helper' require 'stringio' -require 'aws-sdk-core/s3' +require 'aws-sdk-s3' class CodeDeployVersionTrackingTest < InstanceAgentTestCase context 'CodeDeploy version tracking' do diff --git a/test/instance_agent/string_utils_test.rb b/test/instance_agent/string_utils_test.rb new file mode 100644 index 00000000..38eadd64 --- /dev/null +++ b/test/instance_agent/string_utils_test.rb @@ -0,0 +1,48 @@ +require 'test_helper' + +class StringUtilsTest < InstanceAgentTestCase + + def test_underscore_two_words() + assert_equal("download_bundle", InstanceAgent::StringUtils.underscore("DownloadBundle")) + end + + def test_underscore_already_underscored() + assert_equal("download_bundle", InstanceAgent::StringUtils.underscore("download_bundle")) + end + + def test_underscore_two_words_lowercase_first() + assert_equal("download_bundle", InstanceAgent::StringUtils.underscore("downloadBundle")) + end + + def test_underscore_one_word() + assert_equal("install", InstanceAgent::StringUtils.underscore("Install")) + end + + def test_underscore_three_words() + assert_equal("after_allow_traffic", InstanceAgent::StringUtils.underscore("AfterAllowTraffic")) + end + + def test_underscore_four_words() + assert_equal("after_allow_test_traffic", InstanceAgent::StringUtils.underscore("AfterAllowTestTraffic")) + end + + def test_is_camel_case_all_uppercase() + assert_equal(false, InstanceAgent::StringUtils.is_pascal_case("DOWNLOADBUNDLE")) + end + + def test_is_camel_case_all_lowercase() + assert_equal(false, InstanceAgent::StringUtils.is_pascal_case("downloadbundle")) + end + + def test_is_camel_case_first_lowercase() + assert_equal(false, InstanceAgent::StringUtils.is_pascal_case("downloadBundle")) + end + + def test_is_camel_case_second_uppercase() + assert_equal(false, InstanceAgent::StringUtils.is_pascal_case("downloadbUndle")) + end + + def test_is_camel_case_happy_case() + assert_equal(true, InstanceAgent::StringUtils.is_pascal_case("DownloadBundle")) + end +end \ No newline at end of file diff --git a/test/instance_metadata_test.rb b/test/instance_metadata_test.rb index 0821f270..09e58ce4 100644 --- a/test/instance_metadata_test.rb +++ b/test/instance_metadata_test.rb @@ -1,59 +1,79 @@ require 'test_helper' require 'json' +require 'webmock/rspec' +require 'webmock/test_unit' class InstanceMetadataTest < InstanceAgentTestCase + include WebMock::API def self.should_check_status_code(&blk) should 'raise unless status code is 200' do - @instance_doc_response.stubs(:code).returns(503) + stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). + to_return(status: 503, body: @instance_document, headers: {}) assert_raise(&blk) end end context 'The instance metadata service' do setup do + WebMock.disable_net_connect!(allow_localhost: true) region = 'us-east-1' account_id = '123456789012' instance_id = 'i-deadbeef' @partition = 'aws' @host_identifier = "arn:#{@partition}:ec2:#{region}:#{account_id}:instance/#{instance_id}" @instance_document = JSON.dump({"accountId" => account_id, "region" => region, "instanceId" => instance_id}) - @http = mock() - @instance_doc_response = mock() - @partition_response = mock() - @partition_response.stubs(:code).returns("200") - @instance_doc_response.stubs(:code).returns("200") - - @http.stubs(:get).with('/latest/meta-data/services/partition').returns(@partition_response) - @http.stubs(:get).with('/latest/dynamic/instance-identity/document').returns(@instance_doc_response) - Net::HTTP.stubs(:start).yields(@http) + @instance_document_region_whitespace = JSON.dump({"accountId" => account_id, "region" => " us-east-1 \t", "instanceId" => instance_id}) + @token = "mock_token" + + stub_request(:put, 'http://169.254.169.254/latest/api/token'). + with(headers: {'X-aws-ec2-metadata-token-ttl-seconds' => '21600'}). + to_return(status: 200, body: @token, headers: {}) + stub_request(:get, 'http://169.254.169.254/latest/meta-data/services/partition'). + with(headers: {'X-aws-ec2-metadata-token' => @token}). + to_return(status: 200, body: @partition, headers: {}) + stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). + with(headers: {'X-aws-ec2-metadata-token' => @token}). + to_return(status: 200, body: @instance_document, headers: {}) end context 'getting the host identifier' do - setup do - @partition_response.stubs(:body).returns(@partition) - @instance_doc_response.stubs(:body).returns(@instance_document) + should 'call the correct URL' do + InstanceMetadata.host_identifier + assert_requested(:put, 'http://169.254.169.254/latest/api/token', times: 4) + assert_requested(:get, 'http://169.254.169.254/latest/meta-data/services/partition', times: 1) + assert_requested(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document', times: 3) end - should 'connect to the right host' do - Net::HTTP.expects(:start).with('169.254.169.254', 80, :read_timeout => InstanceMetadata::HTTP_TIMEOUT/2, :open_timeout => InstanceMetadata::HTTP_TIMEOUT/2).yields(@http) - InstanceMetadata.host_identifier + should 'return the body' do + assert_equal(@host_identifier, InstanceMetadata.host_identifier) end - should 'call the correct URL' do - @http.expects(:get). - with("/latest/dynamic/instance-identity/document"). - returns(@instance_doc_response) - InstanceMetadata.host_identifier + should 'return the body if IMDSv2 http request status code is not 200' do + stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). + with(headers: {'X-aws-ec2-metadata-token' => @token}). + to_return(status: 503, body: @instance_document, headers: {}) + stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). + with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). + to_return(status: 200, body: @instance_document, headers: {}) + assert_equal(@host_identifier, InstanceMetadata.host_identifier) end - should 'return the body' do + should 'return the body if IMDSv2 http request errors out' do + stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). + with(headers: {'X-aws-ec2-metadata-token' => @token}). + to_raise(StandardError) + stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). + with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). + to_return(status: 200, body: @instance_document, headers: {}) assert_equal(@host_identifier, InstanceMetadata.host_identifier) end should 'strip whitesace in the body' do - @instance_doc_response.stubs(:body).returns(" \t#{@instance_document} ") + stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). + with(headers: {'X-aws-ec2-metadata-token' => @token}). + to_return(status: 200, body: " \t#{@instance_document} ", headers: {}) assert_equal(@host_identifier, InstanceMetadata.host_identifier) end @@ -63,23 +83,40 @@ def self.should_check_status_code(&blk) context 'getting the region' do - setup do - @instance_doc_response.stubs(:body).returns(@instance_document) + should 'call the correct URL' do + InstanceMetadata.region + assert_requested(:put, 'http://169.254.169.254/latest/api/token', times: 1) + assert_requested(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document', times: 1) + end + + should 'return the region part of the AZ' do + assert_equal("us-east-1", InstanceMetadata.region) end - should 'connect to the right host' do - Net::HTTP.expects(:start).with('169.254.169.254', 80, :read_timeout => InstanceMetadata::HTTP_TIMEOUT/2, :open_timeout => InstanceMetadata::HTTP_TIMEOUT/2).yields(@http) - InstanceMetadata.region + should 'return the region if IMDSv2 http request status code is not 200' do + stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). + with(headers: {'X-aws-ec2-metadata-token' => @token}). + to_return(status: 503, body: @instance_document, headers: {}) + stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). + with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). + to_return(status: 200, body: @instance_document, headers: {}) + assert_equal("us-east-1", InstanceMetadata.region) end - should 'call the correct URL' do - @http.expects(:get). - with("/latest/dynamic/instance-identity/document"). - returns(@instance_doc_response) - InstanceMetadata.region + should 'return the region if IMDSv2 http request errors out' do + stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). + with(headers: {'X-aws-ec2-metadata-token' => @token}). + to_raise(StandardError) + stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). + with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). + to_return(status: 200, body: @instance_document, headers: {}) + assert_equal("us-east-1", InstanceMetadata.region) end - should 'return the region part of the AZ' do + should 'strip whitesace in the body' do + stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). + with(headers: {'X-aws-ec2-metadata-token' => @token}). + to_return(status: 200, body: @instance_document_region_whitespace , headers: {}) assert_equal("us-east-1", InstanceMetadata.region) end diff --git a/test/test_helper.rb b/test/test_helper.rb index 160cdb26..6efc7623 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -19,3 +19,4 @@ # require local test helpers. If you need a helper write, # keep this pattern or you'll be punished hard require 'instance_agent_helper' +require 'instance_agent/string_utils' \ No newline at end of file diff --git a/vendor/gems/codedeploy-commands-1.0.0/lib/aws/add_service_wrapper.rb b/vendor/gems/codedeploy-commands-1.0.0/lib/aws/add_service_wrapper.rb new file mode 100644 index 00000000..f46b01fa --- /dev/null +++ b/vendor/gems/codedeploy-commands-1.0.0/lib/aws/add_service_wrapper.rb @@ -0,0 +1,61 @@ +require 'aws-sdk-code-generator' +require 'aws-sdk-core' + +module Aws + + # Registers a new service. + # + # Aws.add_service('SvcName', api: '/path/to/svc.api.json') + # + # Aws::SvcName::Client.new + # #=> # + # + # This implementation is taken from the AwsSdkRubyCodeGenWrapper: + # https://code.amazon.com/packages/AwsSdkRubyCodeGenWrapper/blobs/mainline/--/lib/add_service_wrapper.rb + # + # @param [String] svc_name The name of the service. This will also be + # the namespace under {Aws} unless options[:whitelabel] is true. + # This must be a valid constant name. + # @option options[Required, String,Pathname,Hash] :api A a path to a valid + # Coral2JSON model or a hash of a parsed model. + # @option options[Boolean, nil] :whitelabel If true do not prepend + # "Aws" to the generated module namespace. + # @option options[String, nil] :core_path The path to the aws-sdk-core libs + # if unset it will be inferred from the currently loaded aws-sdk-core. + # @option options[Hash,nil] :waiters + # @option options[Hash,nil] :resources + # @return [Module] Returns the new service module. + def self.add_service(name, options = {}) + api_hash = + case options[:api] + when String,Pathname then JSON.parse(File.read(options[:api])) + when Hash then options[:api] + else raise ArgumentError, 'Missing or invalid api: must be a path to a ' \ + 'valid Coral2JSON model or a hash of a parsed model.' + end + module_name = options[:whitelabel] ? name : "Aws::#{name}" + core_path = options[:core_path] || File.dirname($LOADED_FEATURES.find { |f| f.include? 'aws-sdk-core.rb' }) + + code = AwsSdkCodeGenerator::CodeBuilder.new( + aws_sdk_core_lib_path: core_path, + service: AwsSdkCodeGenerator::Service.new( + name: name, + module_name: module_name, + api: api_hash, + paginators: options[:paginators], + paginators: options[:paginators], + waiters: options[:waiters], + resources: options[:resources], + gem_dependencies: { 'aws-sdk-core' => '3' }, + gem_version: '1.0.0', + ) + ) + begin + Object.module_eval(code.source) + rescue => err + puts(code.source) + raise err + end + Object.const_get(module_name) + end +end diff --git a/vendor/gems/codedeploy-commands-1.0.0/lib/aws/codedeploy_commands.rb b/vendor/gems/codedeploy-commands-1.0.0/lib/aws/codedeploy_commands.rb index b132d641..600800b8 100644 --- a/vendor/gems/codedeploy-commands-1.0.0/lib/aws/codedeploy_commands.rb +++ b/vendor/gems/codedeploy-commands-1.0.0/lib/aws/codedeploy_commands.rb @@ -1,6 +1,7 @@ gem_root = File.dirname(File.dirname(File.dirname(__FILE__))) require 'aws-sdk-core' +require "#{gem_root}/lib/aws/add_service_wrapper" require "#{gem_root}/lib/aws/plugins/certificate_authority" require "#{gem_root}/lib/aws/plugins/deploy_control_endpoint" require "#{gem_root}/lib/aws/plugins/deploy_agent_version" diff --git a/vendor/gems/process_manager-0.0.13/lib/process_manager/master.rb b/vendor/gems/process_manager-0.0.13/lib/process_manager/master.rb index 42366b58..489cf2c1 100644 --- a/vendor/gems/process_manager-0.0.13/lib/process_manager/master.rb +++ b/vendor/gems/process_manager-0.0.13/lib/process_manager/master.rb @@ -51,7 +51,7 @@ def self.restart def self.status if pid = find_pid - if ProcessManager::process_running?(pid) + if pid > 0 and ProcessManager::process_running?(pid) pid else clean_stale_pid diff --git a/vendor/specifications/codedeploy-commands-1.0.0.gemspec b/vendor/specifications/codedeploy-commands-1.0.0.gemspec index 57ef455e..41fb522e 100644 --- a/vendor/specifications/codedeploy-commands-1.0.0.gemspec +++ b/vendor/specifications/codedeploy-commands-1.0.0.gemspec @@ -19,11 +19,11 @@ Gem::Specification.new do |s| s.specification_version = 3 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - s.add_runtime_dependency(%q, ["~> 2.9"]) + s.add_runtime_dependency(%q, ["~> 3"]) else - s.add_dependency(%q, ["~> 2.9"]) + s.add_dependency(%q, ["~> 3"]) end else - s.add_dependency(%q, ["~> 2.9"]) + s.add_dependency(%q, ["~> 3"]) end end