lib/aws/core/configuration.rb



# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
#     http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

require 'set'
require 'uri'

module AWS
  module Core

    # A configuration object for AWS interfaces and clients.
    #
    # == Configuring Credential
    #
    # In order to do anything with AWS you will need to assign credentials.
    # The simplest method is to assing your credentials into the default
    # configuration:
    #
    #   AWS.config(:access_key_id => 'KEY', :secret_access_key => 'SECRET')
    #
    # You can also export them into your environment and they will be picked up
    # automatically:
    #
    #   export AWS_ACCESS_KEY_ID='YOUR_KEY_ID_HERE'
    #   export AWS_SECRET_ACCESS_KEY='YOUR_SECRET_KEY_HERE'
    #
    # For compatability with other AWS gems, the credentials can also be
    # exported like:
    #
    #   export AMAZON_ACCESS_KEY_ID='YOUR_KEY_ID_HERE'
    #   export AMAZON_SECRET_ACCESS_KEY='YOUR_SECRET_KEY_HERE'
    #
    # == Modifying a Configuration
    #
    # Configuration objects are read-only.  If you need a different set of
    # configuration values, call {#with}, passing in the updates
    # and a new configuration object will be returned.
    #
    #   config = Configuration.new(:max_retires => 3)
    #   new_config = config.with(:max_retries => 2)
    #
    #   config.max_retries #=> 3
    #   new_config.max_retries #=> 2
    #
    # == Global Configuration
    #
    # The global default configuration can be found at {AWS.config}
    #
    # @attr_reader [String,nil] access_key_id AWS access key id credential.
    #   Defaults to +nil+.
    #
    # @attr_reader [String] ec2_endpoint The service endpoint for Amazon EC2.
    #   Defaults to 'ec2.amazonaws.com'.
    #
    # @attr_reader [Object] http_handler The http handler that sends requests
    #   to AWS.  Defaults to an HTTP handler built on net/http.
    #
    # @attr_reader [String] iam_endpoint The service endpoint for AWS Idenity
    #   Access Management (IAM).  Defaults to 'iam.amazonaws.com'.
    #
    # @attr_reader [Object,nil] logger A logger instance that 
    #   should receive log messages generated by service requets.  
    #   A logger needs to respond to #log and must accept a 
    #   severity (e.g. :info, :error, etc) and a string message.
    #   Defaults to +nil+.
    #
    # @attr_reader [Integer] max_retries The maximum number of times
    #   service errors (500) should be retried.  There is an exponential 
    #   backoff in between service request retries, so the more retries the
    #   longer it can take to fail.  Defautls to 3.
    #
    # @attr_reader [String, URI, nil] proxy_uri The URI of the proxy 
    #    to send service requests through.  You can pass a URI object or a 
    #    URI string.  Defautls to +nil+.
    #
    #      AWS.config(:proxy_uri => 'https://user:pass@my.proxy:443')
    #
    # @attr_reader [String] s3_endpoint The service endpoint for Amazon S3.
    #   Defaults to 's3.amazonaws.com'.
    #
    # @attr_reader [Integer] s3_multipart_max_parts The maximum number of 
    #   parts to split a file into when uploading in parts to S3.  
    #   Defaults to 1000.
    #
    # @attr_reader [Integer] s3_multipart_threshold (16777216) When uploading
    #   data to S3, if the number of bytes to send exceedes 
    #   +:s3_multipart_threshold+ then a multi part session is automatically
    #   started and the data is sent up in chunks.  The size of each part
    #   is specified by +:s3_multipart_min_part_size+. Defaults to 
    #   16777216 (16MB).
    #
    # @attr_reader [Integer] s3_multipart_min_part_size The absolute minimum
    #   size (in bytes) each S3 multipart segment should be.
    #   Defaults to 5242880 (5MB).
    #
    # @attr_reader [Symbol] s3_server_side_encryption The algorithm to
    #   use when encrypting object data on the server side.  The only
    #   valid value is +:aes256+, which specifies that the object
    #   should be stored using the AES encryption algorithm with 256
    #   bit keys.  Defaults to +nil+, meaning server side encryption
    #   is not used unless specified on each individual call to upload
    #   an object.  This option controls the default behavior for the
    #   following method:
    #
    #   * {S3::S3Object#write}
    #   * {S3::S3Object#multipart_upload}
    #   * {S3::S3Object#copy_from} and {S3::S3Object#copy_to}
    #   * {S3::S3Object#presigned_post}
    #   * {S3::Bucket#presigned_post}
    #
    #   You can construct an interface to Amazon S3 which always
    #   stores data using server side encryption as follows:
    #
    #     s3 = AWS::S3.new(:s3_server_side_encryption => :aes256)
    #
    # @attr_reader [String,nil] secret_access_key AWS secret access key 
    #   credential.  Defaults to +nil+.
    #
    # @attr_reader [String,nil] session_token AWS secret token credential.
    #   Defaults to +nil+.
    #
    # @attr_reader [String] simple_db_endpoint The service endpoint for Amazon
    #   SimpleDB.  Defaults to 'sdb.amazonaws.com'.
    #
    # @attr_reader [Boolean] simple_db_consistent_reads Determines
    #   if all SimpleDB read requests should be done consistently.
    #   Consistent reads are slower, but reflect all changes to SDB.
    #   Defaults to +false+.
    #
    # @attr_reader [String] simple_email_service_endpoint The service endpoint
    #   for Amazon Simple Email Service.  Defaults to 
    #   'email.us-east-1.amazonaws.com'.
    #
    # @attr_reader [Object] signer The request signer.  Defaults to
    #   a default request signer implementation.
    #
    # @attr_reader [String] ssl_ca_file The path to a CA cert bundle in 
    #   PEM format.
    #
    #   If +ssl_verify_peer+ is true (the default) this bundle will be
    #   used to validate the server certificate in each HTTPS request.
    #   The AWS SDK for Ruby ships with a CA cert bundle, which is the
    #   default value for this option.
    #
    # @attr_reader [Boolean] ssl_verify_peer When +true+ 
    #   the HTTP handler validate server certificates for HTTPS requests.
    #   Defaults to +true+.
    #
    #   This option should only be disabled for diagnostic purposes;
    #   leaving this option set to +false+ exposes your application to
    #   man-in-the-middle attacks and can pose a serious security
    #   risk.
    #
    # @attr_reader [Boolean] stub_requests When +true+ requests are not
    #   sent to AWS, instead empty reponses are generated and returned to
    #   each service request.
    #
    # @attr_reader [String] sns_endpoint The service endpoint for Amazon SNS.
    #   Defaults to 'sns.us-east-1.amazonaws.com'.
    #
    # @attr_reader [String] sqs_endpoint The service endpoint for Amazon SQS.
    #   Defaults to 'sqs.us-east-1.amazonaws.com'.
    #
    # @attr_reader [String] sts_endpoint The service endpoint for AWS
    #   Security Token Service.  Defaults to 'sts.amazonaws.com'.
    #
    # @attr_reader [Boolean] use_ssl When +true+, all requests
    #   to AWS are sent using HTTPS instead vanilla HTTP.
    #   Defaults to +true+.
    #
    # @attr_reader [String] user_agent_prefix A string prefix to 
    #   append to all requets against AWS services.  This should be set
    #   for clients and applications built ontop of the aws-sdk gem.
    #   Defaults to +nil+.
    #
    class Configuration
  
      # Creates a new Configuration object.
      # @param options (see AWS.config)
      # @option options (see AWS.config)
      def initialize options = {}
  
        @created = options.delete(:__created__) || {}
  
        options.each_pair do |opt_name, value|
          opt_name = opt_name.to_sym
          if self.class.accepted_options.include?(opt_name)
            supplied[opt_name] = value
          end
        end
  
      end
  
      # Used to create a new Configuration object with the given modifications.
      # The current configuration object is not modified.
      #
      #   AWS.config(:max_retries => 2)
      #
      #   no_retries_config = AWS.config.with(:max_retries => 0)
      #
      #   AWS.config.max_retries        #=> 2
      #   no_retries_config.max_retries #=> 0
      #
      # You can use these configuration objects returned by #with to create
      # AWS objects:
      #
      #   AWS::S3.new(:config => no_retries_config)
      #   AWS::SQS.new(:config => no_retries_config)
      #
      # @param options (see AWS.config)
      # @option options (see AWS.config)
      # @return [Configuration] Copies the current configuration and returns
      #   a new one with modifications as provided in +:options+.
      def with options = {}
  
        # symbolize option keys
        options = options.inject({}) {|h,kv| h[kv.first.to_sym] = kv.last; h }
  
        values = supplied.merge(options)
  
        if supplied == values
          self # nothing changed
        else
          self.class.new(values.merge(:__created__ => @created.dup))
        end
  
      end
  
      # @return [Hash] Returns a hash of all configuration values.
      def to_h
        self.class.accepted_options.inject({}) do |h,k|
          h[k] = send(k)
          h
        end
      end
  
      # @return [Boolean] Returns true if the two configuration objects have
      #   the same values.
      def == other
        other.is_a?(self.class) and self.supplied == other.supplied
      end
  
      alias_method :eql, :==
  
      # @private
      def inspect
        "<#{self.class.name}>"
      end
  
      protected
      def supplied
        @supplied ||= {}
      end
  
      class << self
  
        # @private
        def accepted_options
          @options ||= Set.new
        end
  
        # @private
        def add_option name, default_value = nil, options = {}, &transform
  
          accepted_options << name
  
          define_method(name) do
            value = supplied.has_key?(name) ? supplied[name] : default_value
            transform ? transform.call(value) : value
          end
  
          alias_method("#{name}?", name) if options[:boolean]
  
        end
  
        # Configuration options that have dependencies are re-recreated
        # anytime one of their dependendent configuration values are
        # changed.
        # @private
        def add_option_with_needs name, needs, &create_block
          
          accepted_options << name
  
          define_method(name) do
  
            return supplied[name] if supplied.has_key?(name)
  
            needed = needs.collect{|need| send(need) }
  
            unless @created.key?(name) and @created[name][:needed] == needed
              @created[name] = {}
              @created[name][:object] = create_block.call(self)
              @created[name][:needed] = needed
            end
  
            @created[name][:object]
            
          end
  
        end

        def add_service name, ruby_name, default_endpoint

          create_block = lambda do |config| 
            AWS.const_get(name)::Client.new(:config => config)
          end

          needs = [
            :signer, 
            :http_handler, 
            :"#{ruby_name}_endpoint",
            :max_retries,
            :stub_requests?,
            :proxy_uri,
            :use_ssl?,
            :ssl_verify_peer?,
            :ssl_ca_file,
            :user_agent_prefix,
            :logger,
            :logger_truncate_strings_at,
          ]

          add_option :"#{ruby_name}_endpoint", default_endpoint

          add_option_with_needs :"#{ruby_name}_client", needs, &create_block

        end
  
      end

      add_option :access_key_id, 
        ENV['AWS_ACCESS_KEY_ID'] || ENV['AMAZON_ACCESS_KEY_ID']
  
      add_option :http_handler, Core::Http::NetHttpHandler.new
  
      add_option :logger

      add_option :logger_truncate_strings_at, 1000 
  
      add_option :max_retries, 3
  
      add_option :proxy_uri do |uri| uri ? URI.parse(uri.to_s) : nil end
  
      add_option :secret_access_key, 
        ENV['AWS_SECRET_ACCESS_KEY'] || ENV['AMAZON_SECRET_ACCESS_KEY']
  
      add_option :session_token
  
      add_option_with_needs :signer, 
        [:access_key_id, :secret_access_key, :session_token] do |config|
  
        DefaultSigner.new(
          config.access_key_id, 
          config.secret_access_key, 
          config.session_token)
  
      end
  
      add_option :ssl_verify_peer, true, :boolean => true
  
      add_option :ssl_ca_file, 
        File.expand_path(File.dirname(__FILE__) + "/../../../ca-bundle.crt")
  
      add_option :stub_requests, false, :boolean => true
  
      add_option :use_ssl, true, :boolean => true
  
      add_option :user_agent_prefix
  
    end
  end
end