# frozen_string_literal: true #------------------------------------------------------------------------- # # Copyright (c) Microsoft and contributors. All rights reserved. # # The MIT License(MIT) # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files(the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and / or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions : # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. #-------------------------------------------------------------------------- require "azure/core/signed_service" require "azure/storage/common/core" require "azure/storage/common/service/storage_service_properties" require "azure/storage/common/service/storage_service_stats" module Azure::Storage::Common module Service # A base class for StorageService implementations class StorageService < Azure::Core::SignedService # @!attribute storage_service_host # @return [Hash] Get or set the storage service host attr_accessor :storage_service_host # 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::CommonAuth::SharedKey.new) # @param account_name [String] The account name (optional, Default=Azure::Storage.storage_account_name) # @param options [Azure::Storage::CommonConfigurable] the client configuration context def initialize(signer = nil, account_name = nil, options = {}, &block) StorageService.register_request_callback(&block) if block_given? client_config = options[:client] signer = signer || Azure::Storage::Common::Core::Auth::SharedKey.new( client_config.storage_account_name, client_config.storage_access_key) if client_config.storage_access_key signer = signer || Azure::Storage::Common::Core::Auth::SharedAccessSignatureSigner.new( client_config.storage_account_name, client_config.storage_sas_token) @storage_service_host = { primary: "", secondary: "" }; super(signer, account_name, options) end def call(method, uri, body = nil, headers = {}, options = {}) super(method, uri, body, StorageService.common_headers(options, body).merge(headers), options) end # Public: Get Storage Service properties # # See http://msdn.microsoft.com/en-us/library/azure/hh452239 # See http://msdn.microsoft.com/en-us/library/azure/hh452243 # # ==== Options # # * +:timeout+ - Integer. A timeout in seconds. # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded # in the analytics logs when storage analytics logging is enabled. # # Returns a Hash with the service properties or nil if the operation failed def get_service_properties(options = {}) query = {} StorageService.with_query query, "timeout", options[:timeout].to_s if options[:timeout] response = call(:get, service_properties_uri(query), nil, {}, options) Serialization.service_properties_from_xml response.body end # Public: Set Storage Service properties # # service_properties - An instance of Azure::Storage::CommonService::StorageServiceProperties # # See http://msdn.microsoft.com/en-us/library/azure/hh452235 # See http://msdn.microsoft.com/en-us/library/azure/hh452232 # # ==== Options # # * +:timeout+ - Integer. A timeout in seconds. # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded # in the analytics logs when storage analytics logging is enabled. # # Returns boolean indicating success. def set_service_properties(service_properties, options = {}) query = {} StorageService.with_query query, "timeout", options[:timeout].to_s if options[:timeout] body = Serialization.service_properties_to_xml service_properties call(:put, service_properties_uri(query), body, {}, options) nil end # Public: Retrieves statistics related to replication for the service. # It is only available on the secondary location endpoint when read-access geo-redundant # replication is enabled for the storage account. # # See https://docs.microsoft.com/en-us/rest/api/storageservices/get-blob-service-stats # See https://docs.microsoft.com/en-us/rest/api/storageservices/get-queue-service-stats # See https://docs.microsoft.com/en-us/rest/api/storageservices/get-table-service-stats # # ==== Options # # * +:timeout+ - Integer. A timeout in seconds. # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded # in the analytics logs when storage analytics logging is enabled. # # Returns a Hash with the service statistics or nil if the operation failed def get_service_stats(options = {}) query = {} StorageService.with_query query, "timeout", options[:timeout].to_s if options[:timeout] options.update( location_mode: LocationMode::SECONDARY_ONLY, request_location_mode: RequestLocationMode::SECONDARY_ONLY) response = call(:get, service_stats_uri(query, options), nil, {}, options) Serialization.service_stats_from_xml response.body end # Public: Generate the URI for the service properties # # * +:query+ - see Azure::Storage::CommonServices::GetServiceProperties#call documentation. # # Returns a URI. def service_properties_uri(query = {}) query.update(restype: "service", comp: "properties") generate_uri("", query) end # Public: Generate the URI for the service statistics # # * +:query+ - see Azure::Storage::CommonServices::GetServiceStats#call documentation. # # Returns a URI. def service_stats_uri(query = {}, options = {}) query.update(restype: "service", comp: "stats") generate_uri("", query, options) end # Overrides the base class implementation to determine the request uri # # path - String. the request path # query - Hash. the query parameters # # ==== Options # # * +:encode+ - bool. Specifies whether to encode the path. # * +:location_mode+ - LocationMode. Specifies the location mode used to decide # which location the request should be sent to. # * +:request_location_mode+ - RequestLocationMode. Specifies the location used to indicate # which location the operation (REST API) can be performed against. # This is determined by the API and cannot be specified by the users. # # Returns the uri hash def generate_uri(path = "", query = {}, options = {}) location_mode = if options[:location_mode].nil? LocationMode::PRIMARY_ONLY else options[:location_mode] end request_location_mode = if options[:request_location_mode].nil? RequestLocationMode::PRIMARY_ONLY else request_location_mode = options[:request_location_mode] end location = StorageService.get_location location_mode, request_location_mode if self.client.is_a?(Azure::Storage::Common::Client) && self.client.options[:use_path_style_uri] account_path = get_account_path location path = path.length > 0 ? account_path + "/" + path : account_path end @host = location == StorageLocation::PRIMARY ? @storage_service_host[:primary] : @storage_service_host[:secondary] encode = options[:encode].nil? ? false : options[:encode] if encode path = CGI.escape(path.encode("UTF-8")) # decode the forward slashes to match what the server expects. path = path.gsub(/%2F/, "/") # decode the backward slashes to match what the server expects. path = path.gsub(/%5C/, "/") # Re-encode the spaces (encoded as space) to the % encoding. path = path.gsub(/\+/, "%20") end @host = storage_service_host[:primary] options[:primary_uri] = super path, query @host = storage_service_host[:secondary] options[:secondary_uri] = super path, query if location == StorageLocation::PRIMARY @host = @storage_service_host[:primary] return options[:primary_uri] else @host = @storage_service_host[:secondary] return options[:secondary_uri] end end # Get account path according to the location settings. # # * +:location+ - StorageLocation. Specifies the request location. # # Returns the account path def get_account_path(location) if location == StorageLocation::PRIMARY self.client.options[:storage_account_name] else self.client.options[:storage_account_name] + "-secondary" end 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(&block) @request_callback = block end # Get the request location. # # * +:location_mode+ - LocationMode. Specifies the location mode used to decide # which location the request should be sent to. # * +:request_location_mode+ - RequestLocationMode. Specifies the location used to indicate # which location the operation (REST API) can be performed against. # This is determined by the API and cannot be specified by the users. # # Returns the reqeust location def get_location(location_mode, request_location_mode) if request_location_mode == RequestLocationMode::PRIMARY_ONLY && location_mode == LocationMode::SECONDARY_ONLY raise InvalidOptionsError, "This operation can only be executed against the primary storage location." end if request_location_mode == RequestLocationMode::SECONDARY_ONLY && location_mode == LocationMode::PRIMARY_ONLY raise InvalidOptionsError, "This operation can only be executed against the secondary storage location." end if request_location_mode == RequestLocationMode::PRIMARY_ONLY return StorageLocation::PRIMARY elsif request_location_mode == RequestLocationMode::SECONDARY_ONLY return StorageLocation::SECONDARY end if location_mode == LocationMode::PRIMARY_ONLY || location_mode == LocationMode::PRIMARY_THEN_SECONDARY StorageLocation::PRIMARY elsif location_mode == LocationMode::SECONDARY_ONLY || location_mode == LocationMode::SECONDARY_THEN_PRIMARY StorageLocation::SECONDARY end end # Adds metadata properties to header hash with required prefix # # * +:metadata+ - A Hash of metadata name/value pairs # * +:headers+ - A Hash of HTTP headers def add_metadata_to_headers(metadata, headers) if metadata metadata.each do |key, value| headers["x-ms-meta-#{key}"] = value end end end # Adds a value to the Hash object # # * +:object+ - A Hash object # * +:key+ - The key name # * +:value+ - The value def with_value(object, key, value) object[key] = value.to_s if value end # Adds a header with the value # # * +:headers+ - A Hash of HTTP headers # * +:name+ - The header name # * +:value+ - The value alias with_header with_value # Adds a query parameter # # * +:query+ - A Hash of HTTP query # * +:name+ - The parameter name # * +:value+ - The value alias with_query with_value # Declares a default hash object for request headers def common_headers(options = {}, body = nil) headers = {} headers.merge!("x-ms-client-request-id" => options[:request_id]) if options[:request_id] @request_callback.call(headers) if @request_callback headers end end end end end