fog/fog-atmos

View on GitHub
lib/fog/storage/atmos.rb

Summary

Maintainability
A
3 hrs
Test Coverage
module Fog
  module Storage
    class Atmos < Fog::Service
      autoload :Directory, 'fog/storage/atmos/models/directory'
      autoload :Directories, 'fog/storage/atmos/models/directories'
      autoload :File, 'fog/storage/atmos/models/file'
      autoload :Files, 'fog/storage/atmos/models/files'

      requires :atmos_storage_endpoint,
               :atmos_storage_secret,
               :atmos_storage_token
      recognizes :persistent

      model_path 'fog/storage/atmos/models'
      model       :directory
      collection  :directories
      model       :file
      collection  :files

      request_path 'fog/storage/atmos/requests'
      # request :delete_container
      request :get_namespace
      request :head_namespace
      request :post_namespace
      request :put_namespace
      request :delete_namespace

      module Utils
        ENDPOINT_REGEX = /(https*):\/\/([a-zA-Z0-9\.\-]+)(:[0-9]+)?(\/.*)?/

        def ssl?
          protocol = @endpoint.match(ENDPOINT_REGEX)[1]
          raise ArgumentError, 'Invalid endpoint URL' if protocol.nil?

          return true if protocol == 'https'
          return false if protocol == 'http'

          raise ArgumentError, "Unknown protocol #{protocol}"
        end

        def port
          port = @endpoint.match(ENDPOINT_REGEX)[3]
          return ssl? ? 443 : 80 if port.nil?
          port.split(':')[1].to_i
        end

        def host
          @endpoint.match(ENDPOINT_REGEX)[2]
        end

        def api_path
          @endpoint.match(ENDPOINT_REGEX)[4]
        end

        def setup_credentials(options)
          @storage_token = options[:atmos_storage_token]
          @storage_secret = options[:atmos_storage_secret]
          @storage_secret_decoded = Base64.decode64(@storage_secret)
          @endpoint = options[:atmos_storage_endpoint]
          @prefix = self.ssl? ? 'https' : 'http'
          @storage_host = self.host
          @storage_port = self.port
          @api_path = self.api_path
        end
      end

      class Mock
        include Utils

        def initialize(options={})
          setup_credentials(options)
        end

        def request(options)
          raise "Atmos Storage mocks not implemented"
        end
      end

      class Real
        include Utils

        def initialize(options={})
          setup_credentials(options)
          @connection_options = options[:connection_options] || {}
          @hmac               = Fog::HMAC.new('sha1', @storage_secret_decoded)
          @persistent = options.fetch(:persistent, false)

          @connection = Fog::XML::Connection.new("#{@prefix}://#{@storage_host}:#{@storage_port}",
              @persistent, @connection_options)
        end

        def uid
          @storage_token#.split('/')[-1]
        end

        def sign(string)
          value = @hmac.sign(string)
          Base64.encode64( value ).chomp()
        end

        def reload
          @connection.reset
        end

        def request(params, &block)
          req_path = params[:path]
          # Force set host and port
          params.merge!({
                          :host     => @storage_host,
                          :path     => "#{@api_path}/rest/#{params[:path]}",
                        })
          # Set default method and headers
          params = {:method => 'GET', :headers => {}}.merge params

          params[:headers]["Content-Type"] ||= "application/octet-stream"

          # Add request date
          params[:headers]["date"] = Time.now().httpdate()
          params[:headers]["x-emc-uid"] = @storage_token

          # Build signature string
          signstring = ""
          signstring += params[:method]
          signstring += "\n"
          signstring += params[:headers]["Content-Type"]
          signstring += "\n"
          if( params[:headers]["range"] )
            signstring += params[:headers]["range"]
          end
          signstring += "\n"
          signstring += params[:headers]["date"]
          signstring += "\n"

          signstring += "/rest/" + URI.unescape( req_path ).downcase
          query_str = params[:query].map{|k,v| "#{k}=#{v}"}.join('&')
          signstring += '?' + query_str unless query_str.empty?
          signstring += "\n"

          customheaders = {}
          params[:headers].each { |key,value|
            case key
            when 'x-emc-date', 'x-emc-signature'
              #skip
            when /^x-emc-/
              customheaders[ key.downcase ] = value
            end
          }
          header_arr = customheaders.sort()

          header_arr.each { |key,value|
            # Values are lowercase and whitespace-normalized
            signstring += key + ":" + value.strip.chomp.squeeze( " " ) + "\n"
          }

          digest = @hmac.sign(signstring.chomp())
          signature = Base64.encode64( digest ).chomp()
          params[:headers]["x-emc-signature"] = signature

          params.delete(:host) #invalid excon request parameter

          parse = params.delete(:parse)

          begin
            response = @connection.request(params, &block)
          rescue Excon::Errors::HTTPStatusError => error
            raise case error
            when Excon::Errors::NotFound
              Fog::Storage::Atmos::NotFound.slurp(error)
            else
              error
            end
          end
          unless response.body.empty?
            if parse
              document = Fog::ToHashDocument.new
              parser = Nokogiri::XML::SAX::PushParser.new(document)
              parser << response.body
              parser.finish
              response.body = document.body
            end
          end
          response
        end
      end
    end
  end
end