From 6a07dd63dccffba732ef361d7be433f777adb506 Mon Sep 17 00:00:00 2001 From: mdouchement Date: Thu, 13 Aug 2015 14:34:10 +0200 Subject: [PATCH 1/2] Add Object#copy_from support. Update Readme --- README.md | 83 +++++++++++++++++++++++++++++++++++- lib/swift_storage/account.rb | 3 -- lib/swift_storage/headers.rb | 1 + lib/swift_storage/object.rb | 34 ++++++++++++--- lib/swift_storage/service.rb | 4 +- 5 files changed, 114 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9837c34..5144587 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,88 @@ Or install it yourself as: ## Usage -TODO: Write usage instructions here +```rb +require 'swift-storage' + +# = Configuration = +## With environment variables +# +# SWIFT_STORAGE_AUTH_VERSION +# SWIFT_STORAGE_TENANT +# SWIFT_STORAGE_USERNAME +# SWIFT_STORAGE_PASSWORD +# SWIFT_STORAGE_ENDPOINT +# SWIFT_STORAGE_TEMP_URL_KEY +# +## With Rails initializer +# +# Some parameters are by default configured with the above environment variables, see configuration.rb +SwiftStorage.configure do |config| + config.auth_version = '2.0' # Keystone auth version, default is 1.0 + config.tenant = 'Millennium Falcon' # Aka Openstack project + config.username = 'han' + config.password = 'YT-1300' + config.endpoint = 'https//corellia.lan' # Keystone endpoint + config.temp_url_key = '492727ZED' # Secret key for presigned URLs + # ... +end +# +## With service initialization +# +# NB: It overrides initializer configuration +swift = SwiftStorage::Service.new( + tenant: 'Millennium Falcon', + username: 'han', + password: 'YT-1300', + endpoint: 'https//corellia.lan', + temp_url_key: '492727ZED' +) + + +# Authenticate, primary to retrieve Swift Storage URL +swift.authenticate! +swift.authenticated? +# => true + +# Setup Secret key in Swift server +swift.account.write(temp_url_key: '492727ZED') + +# Create & get containers +swift.containers['source'].create unless swift.containers['source'].exists? +source = swift.containers['source'] +swift.containers['destination'].create unless swift.containers['destination'].exists? +destination = swift.containers['destination'] + +# Get objects +source_obj = source.objects['Kessel.asteroid'] +destination_obj = destination.objects['SiKlaata.cluster'] + +# Upload data into object +source_obj.write('Glitterstim', content_type: 'application/spice') +# or stream from file +File.open('/tmp/Kessel.asteroid', 'r') do |input| + source_obj.write(input, content_type: 'application/spice') +end + +# Copy an object +# Source can be a SwiftStorage::Object or a string like 'source/Kessel.asteroid' +destination_obj.copy_from(source_obj) + +# Read data from Swift +p destination_obj.read +# => Glitterstim + +# Download to a file +File.open('/tmp/SiKlaata.cluster', 'w') do |output| + destination_obj.read(output) +end +# or +destination_obj.stream_to_file('/tmp/SiKlaata.cluster') + +# Create temporary pre-signed URL +p destination_obj.temp_url(Time.now + (3600 * 10), method: :get) +# => https//corellia.lan/v1/AUTH_39c47bfd3ecd41938368239813628963/destination/death/star.moon?temp_url_sig=cbd7568b60abcd5862a96eb03af5fa154e851d54&temp_url_expires=1439430168 +``` ## Contributing diff --git a/lib/swift_storage/account.rb b/lib/swift_storage/account.rb index cbb3ec3..3296c97 100644 --- a/lib/swift_storage/account.rb +++ b/lib/swift_storage/account.rb @@ -29,7 +29,4 @@ def temp_url_key def relative_path '' end - - end - diff --git a/lib/swift_storage/headers.rb b/lib/swift_storage/headers.rb index c65fe2b..a033373 100644 --- a/lib/swift_storage/headers.rb +++ b/lib/swift_storage/headers.rb @@ -18,6 +18,7 @@ module Headers CONTAINER_READ = 'X-Container-Read'.freeze CONTAINER_WRITE = 'X-Container-Write'.freeze ACCOUNT_TEMP_URL_KEY = 'X-Account-Meta-Temp-URL-Key'.freeze + DESTINATION = 'Destination'.freeze end end diff --git a/lib/swift_storage/object.rb b/lib/swift_storage/object.rb index 82571ad..5895d81 100644 --- a/lib/swift_storage/object.rb +++ b/lib/swift_storage/object.rb @@ -153,6 +153,29 @@ def write(input_stream=nil, input_stream end + # Creates a copy of an object that is already stored in Swift. + # + # @param source [String, SwiftStorage::Object] + # The container and object name of the source object or the SwiftStorage::Object source + # + # @param optional_headers [Hash] + # All optional headers supported by Swift API for object copy + # + # @return [SwiftStorage::Object] + # The current object + def copy_from(source, optional_headers = {}) + case source + when SwiftStorage::Object + path = source.relative_path + when String + path = source + else + raise ArgumentError.new('Invalid source type') + end + + request(path, method: :copy, headers: optional_headers.merge(H::DESTINATION => relative_path)) + self + end # Generates a public URL with an expiration time # @@ -184,13 +207,12 @@ def url File.join(service.storage_url, relative_path) end - private - - H = SwiftStorage::Headers - + # Returns the object's relative path (container name with object name) + # + # @return [String] + # The object relative path. + # def relative_path File.join(container.name, name) end - end - diff --git a/lib/swift_storage/service.rb b/lib/swift_storage/service.rb index a61c784..7957822 100644 --- a/lib/swift_storage/service.rb +++ b/lib/swift_storage/service.rb @@ -79,7 +79,7 @@ def create_temp_url(container, object, expires, method, options = {}) string_to_sign = "#{method}\n#{expires}\n#{object_path_unescaped}" - sig = sig_to_hex(hmac('sha1', temp_url_key, string_to_sign)) + sig = sig_to_hex(hmac('sha1', temp_url_key, string_to_sign)) klass = scheme == 'http' ? URI::HTTP : URI::HTTPS @@ -155,6 +155,8 @@ def request(path_or_url, req = Net::HTTP::Post.new(path, headers) when :put req = Net::HTTP::Put.new(path, headers) + when :copy + req = Net::HTTP::Copy.new(path, headers) else raise ArgumentError, "Method #{method} not supported" end From 4bd0822c3c5d8df3417ea488d13d7ea99c39ec93 Mon Sep 17 00:00:00 2001 From: mdouchement Date: Thu, 13 Aug 2015 15:03:55 +0200 Subject: [PATCH 2/2] Add specs --- spec/spec_helper.rb | 4 +++- spec/swift/object_spec.rb | 42 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 371e08f..008821b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -51,8 +51,10 @@ def body(new_body) def random_length Random.rand(5000) + 1000 end +end - +SwiftStorage.configure do |config| + config.auth_version = '1.0' end RSpec::Matchers.define :send_request do |method, path, options={}| diff --git a/spec/swift/object_spec.rb b/spec/swift/object_spec.rb index d5e40f4..cc5d9cf 100644 --- a/spec/swift/object_spec.rb +++ b/spec/swift/object_spec.rb @@ -44,4 +44,46 @@ expect(subject.metadata.jon_doe).to eq('a meta') end + describe '#copy_from' do + subject { swift_service.containers['some_destination_container'].objects['some_copied_object'] } + let(:source) { swift_service.containers['some_source_container'].objects['some_object'] } + + it 'adds optional headers to request' do + expect { subject.copy_from(source, 'X-Object-Falcon' => 'Awesome') }.to send_request( + :copy, + '/v1/AUTH_test/some_source_container/some_object', + headers: { h::DESTINATION => 'some_destination_container/some_copied_object', 'X-Object-Falcon' => 'Awesome' } + ) + end + + context 'when source is a SwiftStorage::Object' do + it 'copies the source' do + expect { subject.copy_from(source) }.to send_request( + :copy, + '/v1/AUTH_test/some_source_container/some_object', + headers: { h::DESTINATION => 'some_destination_container/some_copied_object' } + ) + end + end + + context 'when source is a string' do + it 'copies the source' do + expect { subject.copy_from(source.relative_path) }.to send_request( + :copy, + '/v1/AUTH_test/some_source_container/some_object', + headers: { h::DESTINATION => 'some_destination_container/some_copied_object' } + ) + end + end + + context 'when source is an integer' do + it 'raises an error' do + expect { subject.copy_from(42) }.to raise_error(ArgumentError, 'Invalid source type') + end + end + + it 'returns destination object' do + expect(subject.copy_from(source).relative_path).to eq('some_destination_container/some_copied_object') + end + end end