lib/aws-sdk-core/shared_credentials.rb



module Aws
  class SharedCredentials < Credentials

    # @api private
    KEY_MAP = {
      'aws_access_key_id' => 'access_key_id',
      'aws_secret_access_key' => 'secret_access_key',
      'aws_session_token' => 'session_token',
    }

    # Constructs a new SharedCredentials object. This will load AWS access
    # credentials from an ini file, which supports profiles. The default
    # profile name is 'default'. You can specify the profile name with the
    # `ENV['AWS_PROFILE']` or with the `:profile_name` option.
    #
    # @option [String] :path Path to the shared file.  Defaults
    #   to "#{Dir.home}/.aws/credentials".
    #
    # @option [String] :profile_name Defaults to 'default' or
    #   `ENV['AWS_PROFILE']`.
    #
    def initialize(options = {})
      @path = options[:path] || default_path
      @profile_name = options[:profile_name]
      @profile_name ||= ENV['AWS_PROFILE']
      @profile_name ||= 'default'
      load_from_path if loadable?
    end

    # @return [String]
    attr_reader :path

    # @return [String]
    attr_reader :profile_name

    # @api private
    def inspect
      parts = [
        self.class.name,
        "profile_name=#{profile_name.inspect}",
        "path=#{path.inspect}",
      ]
      "#<#{parts.join(' ')}>"
    end

    # @return [Boolean] Returns `true` if a credential file
    #   exists and has appropriate read permissions at {#path}.
    # @note This method does not indicate if the file found at {#path}
    #   will be parsable, only if it can be read.
    def loadable?
      !path.nil? && File.exists?(path) && File.readable?(path)
    end

    private

    def default_path
      File.join(Dir.home, '.aws', 'credentials')
    rescue ArgumentError
      # Dir.home raises ArgumentError when ENV['home'] is not set
      nil
    end

    def load_from_path
      profile = load_profile
      KEY_MAP.each do |source, target|
        if profile.key?(source)
          instance_variable_set("@#{target}", profile[source])
        end
      end
    end

    def load_profile
      if profile = profiles[profile_name]
        profile
      else
        msg = "Profile `#{profile_name}' not found in #{path}"
        raise Errors::NoSuchProfileError, msg
      end
    end

    def profiles
      ini_parse(File.read(path))
    end

    def ini_parse(file)
      current_section = {}
      map = {}
      file.lines.each do |line|
        line = line.split(/^|\s;/).first # remove comments
        section = line.match(/^\s*\[([^\[\]]+)\]\s*$/) unless line.nil?
        if section
          current_section = section[1]
        elsif current_section
          item = line.match(/^\s*(.+?)\s*=\s*(.+?)\s*$/) unless line.nil?
          if item
            map[current_section] = map[current_section] || {}
            map[current_section][item[1]] = item[2]
          end
        end
      end
      map
    end

  end
end