benny wanda

profilehelpneded
20160501221839s3.rb_copy.docx

# Sample Ruby code for the O'Reilly book "Using AWS Infrastructure

# Services" by James Murty.

#

# This code was written for Ruby version 1.8.6 or greater.

#

# The S3 module implements the REST API of the Amazon Simple Storage Service.

$LOAD_PATH << '.'

require 'AWS'

require 'digest/md5'

class S3

include AWS # Include the AWS module as a mixin

S3_ENDPOINT = "s3.amazonaws.com"

XMLNS = 'http://s3.amazonaws.com/doc/2006-03-01/'

def valid_dns_name(bucket_name)

if bucket_name.size > 63 or bucket_name.size < 3

return false

end

return false unless bucket_name =~ /^[a-z0-9][a-z0-9.-]+$/

return false unless bucket_name =~ /[a-z]/ # Cannot be an IP address

bucket_name.split('.').each do |fragment|

return false if fragment =~ /^-/ or fragment =~ /-$/ or fragment =~ /^$/

end

return true

end

def generate_s3_uri(bucket_name='', object_name='', params=[])

# Decide between the default and sub-domain host name formats

if valid_dns_name(bucket_name)

hostname = bucket_name + "." + S3_ENDPOINT

else

hostname = S3_ENDPOINT

end

# Build an initial secure or non-secure URI for the end point.

request_uri = (@secure_http ? "https://" : "http://") + hostname;

# Include the bucket name in the URI except for alternative hostnames

if hostname == S3_ENDPOINT

request_uri << '/' + URI.escape(bucket_name) if bucket_name != ''

end

# Add object name component to URI if present

request_uri << '/' + URI.escape(object_name) if object_name != ''

# Add request parameters to the URI. Each item in the params variable

# is a hash dictionary containing multiple keys.

query = ""

params.each do |hash|

hash.each do |name, value|

query << '&' if query.length > 0

if value.nil?

query << "#{name}"

else

query << "#{name}=#{CGI::escape(value.to_s)}"

end

end

end

request_uri << "?" + query if query.length > 0

return URI.parse(request_uri)

end

def list_bucket(bucketname='')

#uri = generate_s3_uri('itec427101')

uri = generate_s3_uri(bucketname)

response = do_rest('GET', uri)

buckets = []

xml_doc = REXML::Document.new(response.body)

xml_doc.elements.each('//Buckets/Bucket') do |bucket|

buckets << {

:name => bucket.elements['Name'].text,

:creation_date => bucket.elements['CreationDate'].text

}

end

return {

:owner_id => xml_doc.elements['//Owner/ID'],

:display_name => xml_doc.elements['//Owner/DisplayName'],

:buckets => buckets

}

end

def create_bucket(bucket_name, location=nil)

uri = generate_s3_uri(bucket_name)

if location

xml_doc = REXML::Document.new("<CreateBucketConfiguration/>")

xml_doc.root.add_attribute('xmlns', XMLNS)

xml_doc.root.add_element('LocationConstraint').text = location

do_rest('PUT', uri, xml_doc.to_s, {'Content-Type'=>'text/xml'})

else

do_rest('PUT', uri)

end

return true

end

def delete_bucket(bucket_name)

uri = generate_s3_uri(bucket_name)

do_rest('DELETE', uri)

return true

end

def get_bucket_location(bucket_name)

uri = generate_s3_uri(bucket_name, '', [:location=>nil])

response = do_rest('GET', uri)

xml_doc = REXML::Document.new(response.body)

return xml_doc.elements['LocationConstraint'].text

end

def list_objects(bucket_name, *params)

is_truncated = true

objects = []

prefixes = []

while is_truncated

uri = generate_s3_uri(bucket_name, '', params)

response = do_rest('GET', uri)

xml_doc = REXML::Document.new(response.body)

xml_doc.elements.each('//Contents') do |contents|

objects << {

:key => contents.elements['Key'].text,

:size => contents.elements['Size'].text,

:last_modified => contents.elements['LastModified'].text,

:etag => contents.elements['ETag'].text,

:owner_id => contents.elements['Owner/ID'].text,

:owner_name => contents.elements['Owner/DisplayName'].text

}

end

cps = xml_doc.elements.to_a('//CommonPrefixes')

if cps.length > 0

cps.each do |cp|

prefixes << cp.elements['Prefix'].text

end

end

# Determine whether listing is truncated

is_truncated = 'true' == xml_doc.elements['//IsTruncated'].text

# Remove any existing marker value

params.delete_if {|p| p[:marker]}

# Set the marker parameter to the NextMarker if possible,

# otherwise set it to the last key name in the listing

next_marker_elem = xml_doc.elements['//NextMarker']

last_key_elem = xml_doc.elements['//Contents/Key[last()]']

if next_marker_elem

params << {:marker => next_marker_elem.text}

elsif last_key_elem

params << {:marker => last_key_elem.text}

else

params << {:marker => ''}

end

end

return {

:bucket_name => bucket_name,

:objects => objects,

:prefixes => prefixes

}

end

def create_object(bucket_name, object_key, opts={})

# Initialize local variables for the provided option items

data = (opts[:data] ? opts[:data] : '')

headers = (opts[:headers] ? opts[:headers].clone : {})

metadata = (opts[:metadata] ? opts[:metadata].clone : {})

# The Content-Length header must always be set when data is uploaded.

headers['Content-Length'] =

(data.respond_to?(:stat) ? data.stat.size : data.size).to_s

# Calculate an md5 hash of the data for upload verification

if data.respond_to?(:stat)

# Generate MD5 digest from file data one chunk at a time

md5_digest = Digest::MD5.new

File.open(data.path, 'rb') do |io|

buffer = ''

md5_digest.update(buffer) while io.read(4096, buffer)

end

md5_hash = md5_digest.digest

else

md5_hash = Digest::MD5.digest(data)

end

headers['Content-MD5'] = encode_base64(md5_hash)

# Set the canned policy, may be: 'private', 'public-read',

# 'public-read-write', 'authenticated-read'

headers['x-amz-acl'] = opts[:policy] if opts[:policy]

# Set an explicit content type if none is provided, otherwise the

# ruby HTTP library will use its own default type

# 'application/x-www-form-urlencoded'

if not headers['Content-Type']

headers['Content-Type'] =

data.respond_to?(:to_str) ? 'text/plain' : 'application/octet-stream'

end

# Convert metadata items to headers using the

# S3 metadata header name prefix.

metadata.each do |n,v|

headers["x-amz-meta-#{n}"] = v

end

uri = generate_s3_uri(bucket_name, object_key)

do_rest('PUT', uri, data, headers)

return true

end

# The copy object feature was added to the S3 API after the release of

# "Programming Amazon Web Services" so it is not discussed in the book's

# text. For more details, see:

# http://www.jamesmurty.com/2008/05/06/s3-copy-object-in-beta/

def copy_object(source_bucket_name, source_object_key,

dest_bucket_name, dest_object_key, acl=nil, new_metadata=nil)

headers = {}

# Identify the source object

headers['x-amz-copy-source'] = CGI::escape(

source_bucket_name + '/' + source_object_key)

# Copy metadata from original object, or replace the metadata.

if new_metadata.nil?

headers['x-amz-metadata-directive'] = 'COPY'

else

headers['x-amz-metadata-directive'] = 'REPLACE'

headers.merge!(new_metadata)

end

# The Content-Length header must always be set when data is uploaded.

headers['Content-Length'] = '0'

# Set the canned policy, may be: 'private', 'public-read',

# 'public-read-write', 'authenticated-read'

headers['x-amz-acl'] = acl if acl

uri = generate_s3_uri(dest_bucket_name, dest_object_key)

do_rest('PUT', uri, nil, headers)

return true

end

def delete_object(bucket_name, object_key)

uri = generate_s3_uri(bucket_name, object_key)

do_rest('DELETE', uri)

return true

end

def get_object_metadata(bucket_name, object_key, headers={})

uri = generate_s3_uri(bucket_name, object_key)

response = do_rest('HEAD', uri, nil, headers)

response_headers = {}

metadata = {}

response.each_header do |name,value|

if name.index('x-amz-meta-') == 0

metadata[name['x-amz-meta-'.length..-1]] = value

else

response_headers[name] = value

end

end

return {

:metadata => metadata,

:headers => response_headers

}

end

def get_object(bucket_name, object_key, headers={})

uri = generate_s3_uri(bucket_name, object_key)

if block_given?

response = do_rest('GET', uri, nil, headers) {|segment| yield(segment)}

else

response = do_rest('GET', uri, nil, headers)

end

response_headers = {}

metadata = {}

response.each_header do |name,value|

if name.index('x-amz-meta-') == 0

metadata[name['x-amz-meta-'.length..-1]] = value

else

response_headers[name] = value

end

end

result = {

:metadata => metadata,

:headers => response_headers

}

result[:body] = response.body if not block_given?

return result

end

def get_logging(bucket_name)

uri = generate_s3_uri(bucket_name, '', [:logging=>nil])

response = do_rest('GET', uri)

xml_doc = REXML::Document.new(response.body)

if xml_doc.elements['//LoggingEnabled']

return {

:target_bucket => xml_doc.elements['//TargetBucket'].text,

:target_prefix => xml_doc.elements['//TargetPrefix'].text

}

else

# Logging is not enabled

return nil

end

end

def set_logging(bucket_name, target_bucket=nil,

target_prefix="#{bucket_name}.")

# Build BucketLoggingStatus XML document

xml_doc = REXML::Document.new("<BucketLoggingStatus xmlns='#{XMLNS}'/>")

if target_bucket

logging_enabled = xml_doc.root.add_element('LoggingEnabled')

logging_enabled.add_element('TargetBucket').text = target_bucket

logging_enabled.add_element('TargetPrefix').text = target_prefix

end

uri = generate_s3_uri(bucket_name, '', [:logging=>nil])

do_rest('PUT', uri, xml_doc.to_s, {'Content-Type'=>'application/xml'})

return true

end

def get_acl(bucket_name, object_key='')

uri = generate_s3_uri(bucket_name, object_key, [:acl=>nil])

response = do_rest('GET', uri)

xml_doc = REXML::Document.new(response.body)

grants = []

xml_doc.elements.each('//Grant') do |grant|

grantee = {}

grantee[:type] = grant.elements['Grantee'].attributes['type']

if grantee[:type] == 'Group'

grantee[:uri] = grant.elements['Grantee/URI'].text

else

grantee[:id] = grant.elements['Grantee/ID'].text

grantee[:display_name] = grant.elements['Grantee/DisplayName'].text

end

grants << {

:grantee => grantee,

:permission => grant.elements['Permission'].text

}

end

return {

:owner_id => xml_doc.elements['//Owner/ID'].text,

:owner_name => xml_doc.elements['//Owner/DisplayName'].text,

:grants => grants

}

end

def set_acl(owner_id, bucket_name, object_key='',

grants=[owner_id=>'FULL_CONTROL'])

xml_doc = REXML::Document.new("<AccessControlPolicy xmlns='#{XMLNS}'/>")

xml_doc.root.add_element('Owner').add_element('ID').text = owner_id

grant_list = xml_doc.root.add_element('AccessControlList')

grants.each do |hash|

hash.each do |grantee_id, permission|

grant = grant_list.add_element('Grant')

grant.add_element('Permission').text = permission

# Grantee may be of type email, group, or canonical user

if grantee_id.index('@')

# Email grantee

grantee = grant.add_element('Grantee',

{'xmlns:xsi'=>'http://www.w3.org/2001/XMLSchema-instance',

'xsi:type'=>'AmazonCustomerByEmail'})

grantee.add_element('EmailAddress').text = grantee_id

elsif grantee_id.index('://')

# Group grantee

grantee = grant.add_element('Grantee',

{'xmlns:xsi'=>'http://www.w3.org/2001/XMLSchema-instance',

'xsi:type'=>'Group'})

grantee.add_element('URI').text = grantee_id

else

# Canonical user grantee

grantee = grant.add_element('Grantee',

{'xmlns:xsi'=>'http://www.w3.org/2001/XMLSchema-instance',

'xsi:type'=>'CanonicalUser'})

grantee.add_element('ID').text = grantee_id

end

end

end

uri = generate_s3_uri(bucket_name, object_key, [:acl=>nil])

do_rest('PUT', uri, xml_doc.to_s, {'Content-Type'=>'application/xml'})

return true

end

def set_canned_acl(canned_acl, bucket_name, object_key='')

uri = generate_s3_uri(bucket_name, object_key, [:acl=>nil])

response = do_rest('PUT', uri, nil, {'x-amz-acl'=>canned_acl})

return true

end

def get_torrent(bucket_name, object_key, output)

uri = generate_s3_uri(bucket_name, object_key, [:torrent=>nil])

response = do_rest('GET', uri)

output.write(response.body)

end

def sign_uri(method, expires, bucket_name, object_key='', opts={})

parameters = opts[:parameters] || []

headers = opts[:headers] || {}

headers['Date'] = expires

uri = generate_s3_uri(bucket_name, object_key, parameters)

signature = generate_rest_signature(method, uri, headers)

uri.query = (uri.query.nil? ? '' : "#{uri.query}&")

uri.query << "Signature=" + CGI::escape(signature)

uri.query << "&Expires=" + expires.to_s

uri.query << "&AWSAccessKeyId=" + @aws_access_key

uri.host = bucket_name if opts[:is_virtual_host]

return uri.to_s

end

def build_post_policy(expiration_time, conditions)

if expiration_time.nil? or not expiration_time.respond_to?(:getutc)

raise 'Policy document must include a valid expiration Time object'

end

if conditions.nil? or not conditions.class == Hash

raise 'Policy document must include a valid conditions Hash object'

end

# Convert conditions object mappings to condition statements

conds = []

conditions.each_pair do |name,test|

if test.nil?

# A nil condition value means allow anything.

conds << %{["starts-with", "$#{name}", ""]}

elsif test.is_a? String

conds << %{{"#{name}": "#{test}"}}

elsif test.is_a? Array

conds << %{{"#{name}": "#{test.join(',')}"}}

elsif test.is_a? Hash

operation = test[:op]

value = test[:value]

conds << %{["#{operation}", "$#{name}", "#{value}"]}

elsif test.is_a? Range

conds << %{["#{name}", #{test.begin}, #{test.end}]}

else

raise "Unexpected value type for condition '#{name}': #{test.class}"

end

end

return %{{"expiration": "#{expiration_time.getutc.iso8601}",

"conditions": [#{conds.join(",")}]}}

end

def build_post_form(bucket_name, key, options={})

fields = []

# Form is only authenticated if a policy is specified.

if options[:expiration] or options[:conditions]

# Generate policy document

policy = build_post_policy(options[:expiration], options[:conditions])

puts "POST Policy\n===========\n#{policy}\n\n" if @debug

# Add the base64-encoded policy document as the 'policy' field

policy_b64 = encode_base64(policy)

fields << %{<input type="hidden" name="policy" value="#{policy_b64}">}

# Add the AWS access key as the 'AWSAccessKeyId' field

fields << %{<input type="hidden" name="AWSAccessKeyId"

value="#{@aws_access_key}">}

# Add signature for encoded policy document as the 'AWSAccessKeyId' field

signature = generate_signature(policy_b64)

fields << %{<input type="hidden" name="signature" value="#{signature}">}

end

# Include any additional fields

options[:fields].each_pair do |n,v|

if v.nil?

# Allow users to provide their own <input> fields as text.

fields << n

else

fields << %{<input type="hidden" name="#{n}" value="#{v}">}

end

end if options[:fields]

# Add the vital 'file' input item, which may be a textarea or file.

if options[:text_input]

# Use the text_input option which should specify a textarea or text

# input field. For example:

# '<textarea name="file" cols="80" rows="5">Default Text</textarea>'

fields << options[:text_input]

else

fields << %{<input name="file" type="file">}

end

# Construct a sub-domain URL to refer to the target bucket. The

# HTTPS protocol will be used if the secure HTTP option is enabled.

url = "http#{@secure_http ? 's' : ''}://#{bucket_name}.s3.amazonaws.com/"

# Construct the entire form.

form = %{

<form action="#{url}" method="post" enctype="multipart/form-data">

<input type="hidden" name="key" value="#{key}">

#{fields.join("\n")}

<br>

<input type="submit" value="Upload to Amazon S3">

</form>

}

puts "POST Form\n=========\n#{form}\n" if @debug

return form

end

end