Skip to content

Commit

Permalink
Merge pull request #7 from mdouchement/tp-object_copy
Browse files Browse the repository at this point in the history
Object#copy_from support
  • Loading branch information
kuon committed Aug 13, 2015
2 parents feb2dd1 + 4bd0822 commit 08690cb
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 12 deletions.
83 changes: 82 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 0 additions & 3 deletions lib/swift_storage/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,4 @@ def temp_url_key
def relative_path
''
end


end

1 change: 1 addition & 0 deletions lib/swift_storage/headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
34 changes: 28 additions & 6 deletions lib/swift_storage/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
#
Expand Down Expand Up @@ -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

4 changes: 3 additions & 1 deletion lib/swift_storage/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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={}|
Expand Down
42 changes: 42 additions & 0 deletions spec/swift/object_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 08690cb

Please sign in to comment.