diff --git a/ChangeLog.md b/ChangeLog.md index 86537b81..91b93b83 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,9 @@ +2016.12 - version 0.11.5-preview + +ALL +* Added the support for setting customer user agent. [#71](https://github.com/Azure/azure-storage-ruby/issues/71) +* Added the support for hooking in sending requests. + 2016.11 - version 0.11.4-preview ALL diff --git a/README.md b/README.md index f932ff29..86c6e560 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,19 @@ queues.delete_queue("test-queue") ``` + +## Customize the user-agent + +You can customize the user-agent string by setting your user agent prefix when creating the service client. + +```ruby +# Require the azure storage rubygem +require "azure/storage" + +# Setup a specific instance of an Azure::Storage::Client with :user_agent_prefix option +client = Azure::Storage::Client.create(:storage_account_name => "your account name", :storage_access_key => "your access key", :user_agent_prefix => "your application name") +``` + # Getting Started for Contributors If you would like to become an active contributor to this project please follow the instructions provided in [Azure Projects Contribution Guidelines](http://azure.github.io/guidelines/). diff --git a/azure-storage.gemspec b/azure-storage.gemspec index 0620e6cc..65ce9b88 100644 --- a/azure-storage.gemspec +++ b/azure-storage.gemspec @@ -41,7 +41,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency('azure-core', '~> 0.1') s.add_runtime_dependency('faraday', '~> 0.9') s.add_runtime_dependency('faraday_middleware', '~> 0.10') - s.add_runtime_dependency('nokogiri', '~> 1.6') + s.add_runtime_dependency('nokogiri', '~> 1.6.0') s.add_development_dependency('dotenv', '~> 2.0') s.add_development_dependency('minitest', '~> 5') diff --git a/lib/azure/storage/blob/blob_service.rb b/lib/azure/storage/blob/blob_service.rb index e13df13f..51c3b455 100755 --- a/lib/azure/storage/blob/blob_service.rb +++ b/lib/azure/storage/blob/blob_service.rb @@ -38,10 +38,10 @@ class BlobService < StorageService include Azure::Storage::Blob include Azure::Storage::Blob::Container - def initialize(options = {}) + def initialize(options = {}, &block) client_config = options[:client] || Azure::Storage signer = options[:signer] || Azure::Storage::Core::Auth::SharedKey.new(client_config.storage_account_name, client_config.storage_access_key) - super(signer, client_config.storage_account_name, options) + super(signer, client_config.storage_account_name, options, &block) @host = client.storage_blob_host end diff --git a/lib/azure/storage/client.rb b/lib/azure/storage/client.rb index 1ca78868..556e1d16 100644 --- a/lib/azure/storage/client.rb +++ b/lib/azure/storage/client.rb @@ -27,6 +27,7 @@ require 'azure/storage/client_options' +require 'azure/storage/service/storage_service' require 'azure/storage/blob/blob_service' require 'azure/storage/table/table_service' require 'azure/storage/queue/queue_service' @@ -59,6 +60,7 @@ class Client # * +:default_endpoints_protocol+ - String. http or https # * +:use_path_style_uri+ - String. Whether use path style URI for specified endpoints # * +:ca_file+ - String. File path of the CA file if having issue with SSL + # * +:user_agent_prefix+ - String. The user agent prefix that can identify the application calls the library # # The valid set of options inlcude: # * Storage Emulator: +:use_development_storage+ required, +:development_storage_proxy_uri+ optionally @@ -76,19 +78,24 @@ class Client # When empty options are given, it will try to read settings from Environment Variables. Refer to [Azure::Storage::ClientOptions.env_vars_mapping] for the mapping relationship # # @return [Azure::Storage::Client] - def initialize(options = {}) + def initialize(options = {}, &block) if options.is_a?(Hash) options = setup_options if options.length == 0 options = parse_connection_string(options[:storage_connection_string]) if options[:storage_connection_string] - end + if options.has_key?(:user_agent_prefix) + Azure::Storage::Service::StorageService.user_agent_prefix = options[:user_agent_prefix] + options.delete :user_agent_prefix + end + end + Azure::Storage::Service::StorageService.register_request_callback &block if block_given? reset!(options) end # Azure Blob service client configured from this Azure Storage client instance # @return [Azure::Storage::Blob::BlobService] - def blob_client(options = {}) - @blob_client ||= Azure::Storage::Blob::BlobService.new(default_client(options)) + def blob_client(options = {}, &block) + @blob_client ||= Azure::Storage::Blob::BlobService.new(default_client(options), &block) end # Azure Queue service client configured from this Azure Storage client instance @@ -104,7 +111,6 @@ def table_client(options = {}) end class << self - # Public: Creates an instance of [Azure::Storage::Client] # # ==== Attributes @@ -127,6 +133,7 @@ class << self # * +:default_endpoints_protocol+ - String. http or https # * +:use_path_style_uri+ - String. Whether use path style URI for specified endpoints # * +:ca_file+ - String. File path of the CA file if having issue with SSL + # * +:user_agent_prefix+ - String. The user agent prefix that can identify the application calls the library # # The valid set of options inlcude: # * Storage Emulator: +:use_development_storage+ required, +:development_storage_proxy_uri+ optionally @@ -144,8 +151,8 @@ class << self # When empty options are given, it will try to read settings from Environment Variables. Refer to [Azure::Storage::ClientOptions.env_vars_mapping] for the mapping relationship # # @return [Azure::Storage::Client] - def create(options={}) - Client.new(options) + def create(options={}, &block) + Client.new(options, &block) end # Public: Creates an instance of [Azure::Storage::Client] with Storage Emulator @@ -155,17 +162,16 @@ def create(options={}) # * +proxy_uri+ - String. Used with +:use_development_storage+ if emulator is hosted other than localhost. # # @return [Azure::Storage::Client] - def create_development(proxy_uri=nil) + def create_development(proxy_uri=nil, &block) proxy_uri ||= StorageServiceClientConstants::DEV_STORE_URI - create(:use_development_storage => true, :development_storage_proxy_uri => proxy_uri) + create(:use_development_storage => true, :development_storage_proxy_uri => proxy_uri, &block) end - # Public: Creates an instance of [Azure::Storage::Client] from Environment Variables # # @return [Azure::Storage::Client] - def create_from_env - create + def create_from_env(&block) + create &block end # Public: Creates an instance of [Azure::Storage::Client] from Environment Variables @@ -175,8 +181,8 @@ def create_from_env # * +connection_string+ - String. Please refer to https://azure.microsoft.com/en-us/documentation/articles/storage-configure-connection-string/. # # @return [Azure::Storage::Client] - def create_from_connection_string(connection_string) - Client.new(connection_string) + def create_from_connection_string(connection_string, &block) + Client.new(connection_string, &block) end end diff --git a/lib/azure/storage/queue/queue_service.rb b/lib/azure/storage/queue/queue_service.rb index 311fb8d6..ef412216 100644 --- a/lib/azure/storage/queue/queue_service.rb +++ b/lib/azure/storage/queue/queue_service.rb @@ -30,10 +30,10 @@ module Queue include Azure::Storage::Service class QueueService < StorageService - def initialize(options = {}) + def initialize(options = {}, &block) client_config = options[:client] || Azure::Storage signer = options[:signer] || Azure::Storage::Core::Auth::SharedKey.new(client_config.storage_account_name, client_config.storage_access_key) - super(signer, client_config.storage_account_name, options) + super(signer, client_config.storage_account_name, options, &block) @host = @client.storage_queue_host end diff --git a/lib/azure/storage/service/storage_service.rb b/lib/azure/storage/service/storage_service.rb index f0bcad45..833be4ac 100755 --- a/lib/azure/storage/service/storage_service.rb +++ b/lib/azure/storage/service/storage_service.rb @@ -33,10 +33,11 @@ class StorageService < Azure::Core::SignedService # Create a new instance of the StorageService # # @param signer [Azure::Core::Auth::Signer] An implementation of Signer used for signing requests. - # (optional, Default=Azure::Storage::Auth::SharedKey.new) + # (optional, Default=Azure::Storage::Auth::SharedKey.new) # @param account_name [String] The account name (optional, Default=Azure::Storage.storage_account_name) # @param options [Azure::Storage::Configurable] the client configuration context - def initialize(signer=nil, account_name=nil, options = {}) + def initialize(signer=nil, account_name=nil, options = {}, &block) + StorageService.register_request_callback &block if block_given? options[:client] = Azure::Storage if options[:client] == nil client_config = options[:client] signer = signer || Azure::Storage::Core::Auth::SharedKey.new( @@ -118,6 +119,20 @@ def generate_uri(path='', query={}) end class << self + # @!attribute user_agent_prefix + # @return [Proc] Get or set the user agent prefix + attr_accessor :user_agent_prefix + + # @!attribute request_callback + # @return [Proc] The callback before the request is signed and sent + attr_reader :request_callback + + # Registers the callback when sending the request + # The headers in the request can be viewed or changed in the code block + def register_request_callback + @request_callback = Proc.new + end + # Adds metadata properties to header hash with required prefix # # metadata - A Hash of metadata name/value pairs @@ -157,9 +172,10 @@ def with_value(object, key, value) def common_headers(options = {}) headers = { 'x-ms-version' => Azure::Storage::Default::STG_VERSION, - 'User-Agent' => Azure::Storage::Default::USER_AGENT + 'User-Agent' => user_agent_prefix ? "#{user_agent_prefix}; #{Azure::Storage::Default::USER_AGENT}" : Azure::Storage::Default::USER_AGENT } headers.merge!({'x-ms-client-request-id' => options[:request_id]}) if options[:request_id] + @request_callback.call(headers) if @request_callback headers end end diff --git a/lib/azure/storage/table/table_service.rb b/lib/azure/storage/table/table_service.rb index dc905cb5..4a4ad5fa 100755 --- a/lib/azure/storage/table/table_service.rb +++ b/lib/azure/storage/table/table_service.rb @@ -31,10 +31,10 @@ module Azure::Storage module Table class TableService < Service::StorageService - def initialize(options = {}) + def initialize(options = {}, &block) client_config = options[:client] || Azure::Storage signer = options[:signer] || Auth::SharedKey.new(client_config.storage_account_name, client_config.storage_access_key) - super(signer, client_config.storage_account_name, options) + super(signer, client_config.storage_account_name, options, &block) @host = client.storage_table_host end diff --git a/lib/azure/storage/version.rb b/lib/azure/storage/version.rb index 1293f6f2..46fd6ccb 100644 --- a/lib/azure/storage/version.rb +++ b/lib/azure/storage/version.rb @@ -28,7 +28,7 @@ class Version # Fields represent the parts defined in http://semver.org/ MAJOR = 0 unless defined? MAJOR MINOR = 11 unless defined? MINOR - UPDATE = 4 unless defined? UPDATE + UPDATE = 5 unless defined? UPDATE PRE = 'preview' unless defined? PRE class << self diff --git a/test/integration/blob/container/container_metadata_test.rb b/test/integration/blob/container/container_metadata_test.rb index a00ebdcf..80e9094c 100644 --- a/test/integration/blob/container/container_metadata_test.rb +++ b/test/integration/blob/container/container_metadata_test.rb @@ -25,7 +25,12 @@ require "azure/storage/blob/blob_service" describe Azure::Storage::Blob::BlobService do - subject { Azure::Storage::Blob::BlobService.new } + let(:user_agent_prefix) { 'azure_storage_ruby_integration_test' } + subject { + Azure::Storage::Blob::BlobService.new { |headers| + headers['User-Agent'] = "#{user_agent_prefix}; #{headers['User-Agent']}" + } + } after { ContainerNameHelper.clean } describe '#set/get_container_metadata' do diff --git a/test/integration/queue/create_queue_test.rb b/test/integration/queue/create_queue_test.rb index e5f0db12..d7c60859 100644 --- a/test/integration/queue/create_queue_test.rb +++ b/test/integration/queue/create_queue_test.rb @@ -26,7 +26,12 @@ require "azure/core/http/http_error" describe Azure::Storage::Queue::QueueService do - subject { Azure::Storage::Queue::QueueService.new } + let(:user_agent_prefix) { 'azure_storage_ruby_integration_test' } + subject { + Azure::Storage::Queue::QueueService.new { |headers| + headers['User-Agent'] = "#{user_agent_prefix}; #{headers['User-Agent']}" + } + } describe '#create_queue' do let(:queue_name){ QueueNameHelper.name } diff --git a/test/integration/table/create_table_test.rb b/test/integration/table/create_table_test.rb index 04e85461..b451356d 100644 --- a/test/integration/table/create_table_test.rb +++ b/test/integration/table/create_table_test.rb @@ -27,7 +27,12 @@ describe Azure::Storage::Table::TableService do describe "#create_table" do - subject { Azure::Storage::Table::TableService.new } + let(:user_agent_prefix) { 'azure_storage_ruby_integration_test' } + subject { + Azure::Storage::Table::TableService.new { |headers| + headers['User-Agent'] = "#{user_agent_prefix}; #{headers['User-Agent']}" + } + } let(:table_name){ TableNameHelper.name } after { TableNameHelper.clean } diff --git a/test/unit/blob/blob_service_test.rb b/test/unit/blob/blob_service_test.rb index b9532514..babf9a1c 100644 --- a/test/unit/blob/blob_service_test.rb +++ b/test/unit/blob/blob_service_test.rb @@ -30,14 +30,18 @@ require 'azure/storage/service/signed_identifier' describe Azure::Storage::Blob::BlobService do - - subject { Azure::Storage::Blob::BlobService.new } + let(:user_agent_prefix) { 'azure_storage_ruby_unit_test' } + subject { + Azure::Storage::Blob::BlobService.new { |headers| + headers['User-Agent'] = "#{user_agent_prefix}; #{headers['User-Agent']}" + } + } let(:serialization) { Azure::Storage::Blob::Serialization } let(:uri) { URI.parse 'http://foo.com' } let(:query) { {} } let(:x_ms_version) { Azure::Storage::Default::STG_VERSION } let(:user_agent) { Azure::Storage::Default::USER_AGENT } - let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => user_agent} } + let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => "#{user_agent_prefix}; #{user_agent}"} } let(:request_body) { 'request-body' } let(:response_headers) { {} } @@ -186,7 +190,7 @@ 'x-ms-meta-MetadataKey' => 'MetaDataValue', 'x-ms-meta-MetadataKey1' => 'MetaDataValue1', 'x-ms-version' => x_ms_version, - 'User-Agent' => user_agent + 'User-Agent' => "#{user_agent_prefix}; #{user_agent}" } subject.stubs(:container_uri).with(container_name, {}).returns(uri) serialization.stubs(:container_from_headers).with(response_headers).returns(container) @@ -204,7 +208,7 @@ let(:options) { {:public_access_level => public_access_level} } before do - request_headers = {'x-ms-blob-public-access' => public_access_level, 'x-ms-version' => x_ms_version, 'User-Agent' => user_agent} + request_headers = {'x-ms-blob-public-access' => public_access_level, 'x-ms-version' => x_ms_version, 'User-Agent' => "#{user_agent_prefix}; #{user_agent}"} subject.stubs(:container_uri).with(container_name, {}).returns(uri) subject.stubs(:call).with(verb, uri, nil, request_headers, options).returns(response) serialization.stubs(:container_from_headers).with(response_headers).returns(container) @@ -469,7 +473,7 @@ {'x-ms-meta-MetadataKey' => 'MetaDataValue', 'x-ms-meta-MetadataKey1' => 'MetaDataValue1', 'x-ms-version' => x_ms_version, - 'User-Agent' => user_agent + 'User-Agent' => "#{user_agent_prefix}; #{user_agent}" } } @@ -632,7 +636,7 @@ 'x-ms-blob-content-length' => blob_length.to_s, 'x-ms-sequence-number' => 0.to_s, 'x-ms-version' => x_ms_version, - 'User-Agent' => user_agent + 'User-Agent' => "#{user_agent_prefix}; #{user_agent}" } } @@ -744,7 +748,7 @@ 'x-ms-range' => "bytes=#{start_range}-#{end_range}", 'Content-Type' => '', 'x-ms-version' => x_ms_version, - 'User-Agent' => user_agent + 'User-Agent' => "#{user_agent_prefix}; #{user_agent}" } } @@ -841,7 +845,7 @@ 'x-ms-page-write' => 'clear', 'Content-Type' => '', 'x-ms-version' => x_ms_version, - 'User-Agent' => user_agent + 'User-Agent' => "#{user_agent_prefix}; #{user_agent}" } } @@ -905,7 +909,7 @@ let(:content) { 'some content' } let(:block_id) { 'block-id' } let(:server_generated_content_md5) { 'server-content-md5' } - let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => user_agent} } + let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => "#{user_agent_prefix}; #{user_agent}"} } before { query.update({'comp' => 'block', 'blockid' => Base64.strict_encode64(block_id)}) @@ -952,7 +956,7 @@ { 'x-ms-blob-type' => 'BlockBlob', 'x-ms-version' => x_ms_version, - 'User-Agent' => user_agent + 'User-Agent' => "#{user_agent_prefix}; #{user_agent}" } } @@ -1048,7 +1052,7 @@ let(:verb) { :put } let(:request_body) { 'body' } let(:block_list) { mock() } - let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => user_agent} } + let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => "#{user_agent_prefix}; #{user_agent}"} } before { query.update({'comp' => 'blocklist'}) @@ -1294,7 +1298,7 @@ describe 'when both start_range and end_range are provided' do let(:start_range) { 255 } let(:end_range) { 512 } - let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => user_agent} } + let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => "#{user_agent_prefix}; #{user_agent}"} } it 'modifies the request headers with the desired range' do request_headers['x-ms-range'] = "bytes=#{start_range}-#{end_range}" @@ -1348,7 +1352,7 @@ let(:verb) { :put } let(:query) { {'comp' => 'properties'} } let(:size) { 2048 } - let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => user_agent, 'x-ms-blob-content-length' => size.to_s} } + let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => "#{user_agent_prefix}; #{user_agent}", 'x-ms-blob-content-length' => size.to_s} } before { subject.stubs(:blob_uri).with(container_name, blob_name, query).returns(uri) @@ -1396,7 +1400,7 @@ let(:query) { {'comp' => 'properties'} } let(:action) { :update } let(:number) { 1024 } - let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => user_agent} } + let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => "#{user_agent_prefix}; #{user_agent}"} } before { subject.stubs(:blob_uri).with(container_name, blob_name, query).returns(uri) @@ -1479,7 +1483,7 @@ 'x-ms-blob-type' => 'AppendBlob', 'Content-Length' => 0.to_s, 'x-ms-version' => x_ms_version, - 'User-Agent' => user_agent + 'User-Agent' => "#{user_agent_prefix}; #{user_agent}" } } @@ -1608,7 +1612,7 @@ let(:lease_id) { 'lease_id' } let(:max_size) { 123 } let(:append_position) { 999 } - let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => user_agent} } + let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => "#{user_agent_prefix}; #{user_agent}"} } before { query.update({'comp' => 'appendblock'}) @@ -1693,7 +1697,7 @@ describe '#set_blob_properties' do let(:verb) { :put } - let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => user_agent} } + let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => "#{user_agent_prefix}; #{user_agent}"} } before { query.update({'comp' => 'properties'}) @@ -1793,7 +1797,7 @@ describe '#set_blob_metadata' do let(:verb) { :put } let(:blob_metadata) { {'MetadataKey' => 'MetaDataValue', 'MetadataKey1' => 'MetaDataValue1'} } - let(:request_headers) { {'x-ms-meta-MetadataKey' => 'MetaDataValue', 'x-ms-meta-MetadataKey1' => 'MetaDataValue1', 'x-ms-version' => x_ms_version, 'User-Agent' => user_agent} } + let(:request_headers) { {'x-ms-meta-MetadataKey' => 'MetaDataValue', 'x-ms-meta-MetadataKey1' => 'MetaDataValue1', 'x-ms-version' => x_ms_version, 'User-Agent' => "#{user_agent_prefix}; #{user_agent}"} } before { query.update({'comp' => 'metadata'}) @@ -1820,7 +1824,7 @@ describe '#get_blob_properties' do let(:verb) { :head } - let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => user_agent} } + let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => "#{user_agent_prefix}; #{user_agent}"} } before { subject.stubs(:blob_uri).with(container_name, blob_name, query).returns(uri) @@ -1868,7 +1872,7 @@ describe '#get_blob_metadata' do let(:verb) { :get } - let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => user_agent} } + let(:request_headers) { {'x-ms-version' => x_ms_version, 'User-Agent' => "#{user_agent_prefix}; #{user_agent}"} } before { query['comp']='metadata'