Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to copy a specified version of an object? #969

Closed
christiangenco opened this issue Oct 26, 2015 · 7 comments
Closed

How to copy a specified version of an object? #969

christiangenco opened this issue Oct 26, 2015 · 7 comments
Labels
feature-request A feature should be added or improved.

Comments

@christiangenco
Copy link

The Amazon REST API Documentation cites the following:

Sample Request: Copying a specified version of an object

The following request copies the key my-image.jpg with the specified version ID and copies it into the bucket bucket and gives it the key my-second-image.jpg.

PUT /my-second-image.jpg HTTP/1.1
Host: bucket.s3.amazonaws.com
Date: Wed, 28 Oct 2009 22:32:00 GMT
x-amz-copy-source: /bucket/my-image.jpg?versionId=3/L4kqtJlcpXroDTDmJ+rmSpXd3dIbrHY+MTRCxf3vjVBH40Nr8X8gdRQBpUMLUo
Authorization: authorization string

I see the copy_from and copy_to methods in Aws::S3::Object, but I can't for the life of me figure out how to specify a versionId in either of these requests.

Am I missing something painfully obvious, or should I resort to a raw REST request?

@christiangenco
Copy link
Author

Here's one of the more probable things I've tried:

source_object = Aws::S3::ObjectVersion.new({
  bucket_name: "source_bucket",
  object_key: "test.txt",
  id: "WYIIJB3UpKixITFbqLP6nVbAJsa5Ul5F",
  client: Aws::S3::Bucket.new(name: "source_bucket").client # there's probably an easier way to get a client object
})

destination_object = Aws::S3::Bucket.new("destination_bucket").object("test.txt")

source_object.copy_to(destination_object) 
# NoMethodError: undefined method `copy_to' for #<Aws::S3::ObjectVersion:0x007fb7f9052d88>

destination_object.copy_from(source_object) 
# ArgumentError: expected source to be an Aws::S3::Object, Hash, or String
# from .rbenv/versions/2.1.1/lib/ruby/gems/2.1.0/gems/aws-sdk-resources-2.1.30/lib/aws-sdk-resources/services/s3/object_copier.rb:44:in `copy_source'

@christiangenco
Copy link
Author

Looks like that copy_from didn't work because S3::ObjectCopier#copy_source doesn't support S3::ObjectVersion as a possible argument (and unlike version 1, S3::ObjectVersion is not a subclass of S3::Object).

I thought the #copy_source method might be monkey patched to fix this:

module Aws
  module S3
    class ObjectCopier
      def copy_source(source)
        case source
        when String then escape(source)
        when Hash then "#{source[:bucket]}/#{escape(source[:key])}"
        when S3::Object then "#{source.bucket_name}/#{escape(source.key)}"
        when S3::ObjectVersion then "#{source.bucket_name}/#{escape(source.key)}?versionId=#{escape(source.id)}"
        else
          msg = "expected source to be an Aws::S3::Object, AWS::S3::ObjectVersion, Hash, or String"
          raise ArgumentError, msg
        end
      end
    end
  end
end

but now destination_object.copy_from(source_object) throws:

NotImplementedError: #load not defined for Aws::S3::ObjectVersion
from .rbenv/versions/2.1.1/lib/ruby/gems/2.1.0/gems/aws-sdk-resources-2.1.30/lib/aws-sdk-resources/resource.rb:150:in `load'

I can't find an actual definition for Aws::S3::ObjectVersion (it looks like it's being dynamically created from Aws::Resources::Resource?), so I'm not sure how to implement this mysterious #load method. It might just be assigning a LoadOperation, but I'm in over my head at this point. The only way I see forward at the moment is to define Aws::S3::ObjectVersion identically to how Aws::S3::Object is currently defined and hope the load operation sorts itself out.

I'll start down the road of submitting a raw REST request, though figuring out how to sign requests outside of this gem looks like it might be just as difficult.

@trevorrowe
Copy link
Member

This is currently a limitation of the #copy_from and #copy_to methods. As a workaround you use the Aws::S3::Client#copy_from method directly:

s3 = Aws::S3::Client.new
s3.copy_from({
  bucket: 'target-bucket',
  key: 'target-key',
  copy_source: "source-bucket/source-key?versionId=123456789..."
})

You need to ensure the source key is correctly escaped. You can call Seahorse::Util.uri_path_escape(key) to ensure the key is correctly escaped. You don't have to worry about the bucket name, S3 limits bucket names to characters that do not require escaping. You might have to call Seahorse::Util.uri_escape on the version id value. I don't know what characters might appear in the version ID, but escaping the ID should be perfectly reasonable.

I'll mark this issue as a feature request to add support for copying versioned IDs to the resource interfaces as a feature request.

@trevorrowe trevorrowe added feature-request A feature should be added or improved. Version 2 labels Oct 26, 2015
@christiangenco
Copy link
Author

Ahh perfect! Thank you so much 😄

For anyone else walking down this path, check out the full documentation on that Aws::S3::Client#copy_object method.

Also, it looks like you just added Seahorse::Util#uri_path_escape and it hasn't been published to rubygems yet, so I just patched it in an init script:

if !defined? Seahorse::Util.uri_path_escape
  module Seahorse
    module Util
      class << self
        def uri_path_escape(path)
          path.gsub(/[^\/]+/) { |part| uri_escape(part) }
        end
      end
    end
  end
end

Those are some nifty helper methods - URI escaping is always strangely confusing in Ruby. It'd be lovely if you could just do String#escape or String#escape_url, but I digress.

awood45 added a commit that referenced this issue Nov 11, 2015
@awood45
Copy link
Member

awood45 commented Nov 11, 2015

Added this to the feature request backlog.

@lwoggardner
Copy link

Not sure how to "vote" for feature requests, so I'll just "me too" here.

@mullermp
Copy link
Contributor

Reopening - deprecating usage of Feature Requests backlog markdown file.

@mullermp mullermp reopened this Oct 21, 2019
@mullermp mullermp removed the v2 label Oct 21, 2019
@mullermp mullermp closed this as completed Jul 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request A feature should be added or improved.
Projects
None yet
Development

No branches or pull requests

5 participants