qoobaa-s3 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ *.gem
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jakub Kuźma, Mirosław Boruta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ = s3
2
+
3
+ Description goes here.
4
+
5
+ == Copyright
6
+
7
+ Copyright (c) 2009 Jakub Kuźma. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = "s3"
10
+ gem.summary = %Q{Library for accessing S3 objects and buckets, with command line tool}
11
+ gem.email = "qoobaa@gmail.com"
12
+ gem.homepage = "http://github.com/qoobaa/s3"
13
+ gem.authors = ["Jakub Kuźma", "Mirosław Boruta"]
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/*_test.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/*_test.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ if File.exist?('VERSION.yml')
47
+ config = YAML.load(File.read('VERSION.yml'))
48
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
49
+ else
50
+ version = ""
51
+ end
52
+
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = "s3 #{version}"
55
+ rdoc.rdoc_files.include('README*')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ end
58
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.3
data/bin/s3cmd.rb ADDED
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.expand_path(File.dirname(__FILE__) + "/../lib")
4
+
5
+ require "trollop"
6
+ require "s3"
7
+
8
+ # HELPER METHODS
9
+
10
+ include S3
11
+
12
+ def list_buckets(service)
13
+ service.buckets.each do |bucket|
14
+ puts bucket.name
15
+ end
16
+ end
17
+
18
+ def create_bucket(service, name, location)
19
+ service.buckets.build(name).save(location)
20
+ end
21
+
22
+ def destroy_bucket(service, name)
23
+ service.buckets.find(name).destroy
24
+ end
25
+
26
+ def show_bucket(service, name, options = {})
27
+ service.buckets.find(name).objects.find_all.each do |object|
28
+ puts "#{name}/#{object.key}"
29
+ end
30
+ end
31
+
32
+ def list_objects(service)
33
+ service.buckets.each do |bucket|
34
+ bucket.objects.each do |object|
35
+ puts "#{bucket.name}/#{object.key}"
36
+ end
37
+ end
38
+ end
39
+
40
+ def create_object(service, name, file_name, options = {})
41
+ bucket_name, object_name = name.split("/", 2)
42
+ object = service.buckets.find(bucket_name).objects.build(object_name)
43
+ object.content_type = options[:type]
44
+ object.content_encoding = options[:encoding]
45
+ object.content_disposition = options[:disposition]
46
+ object.acl = options[:acl]
47
+ object.content = File.new(file_name)
48
+ object.save
49
+ end
50
+
51
+ def destroy_object(service, name)
52
+ bucket_name, object_name = name.split("/", 2)
53
+ object = service.buckets.find(bucket_name).objects.find(object_name)
54
+ object.destroy
55
+ end
56
+
57
+ def show_object(service, name, file_name = nil)
58
+ bucket_name, object_name = name.split("/", 2)
59
+ object = service.buckets.find(bucket_name).objects.find_first(object_name)
60
+ puts " object: #{object.name}/#{object.key}"
61
+ puts " content type: #{object.content_type}"
62
+ puts " size: #{object.size}"
63
+ puts " etag: #{object.etag}"
64
+ puts " last modified: #{object.last_modified}"
65
+ # puts "content disposition: #{object.content_disposition}"
66
+ if file_name
67
+ if file_name == "-"
68
+ puts object.content
69
+ else
70
+ File.open(file_name, "wb") do |file|
71
+ file.write(object.content)
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ # COMMAND LINE PARSER
78
+
79
+ ACCESS_KEY_ID = ENV["ACCESS_KEY_ID"]
80
+ SECRET_ACCESS_KEY = ENV["SECRET_ACCESS_KEY"]
81
+ COMMANDS = %w(bucket object)
82
+ BUCKET_SUBCOMMANDS = %w(add remove show)
83
+ OBJECT_SUBCOMMANDS = %w(add remove show)
84
+
85
+ global_options = Trollop::options do
86
+ banner "s3.rb"
87
+ opt :access_key_id, "Your access key id to AWS", :type => :string, :default => ACCESS_KEY_ID
88
+ opt :secret_access_key, "Your secret access key to AWS", :type => :string, :default => SECRET_ACCESS_KEY
89
+ opt :debug, "Debug mode", :type => :flag, :default => false
90
+ stop_on COMMANDS
91
+ end
92
+
93
+ Trollop::die "No access key id given" unless global_options[:access_key_id]
94
+ Trollop::die "No secret access key given" unless global_options[:secret_access_key]
95
+
96
+ service = Service.new(:access_key_id => global_options[:access_key_id],
97
+ :secret_access_key => global_options[:secret_access_key],
98
+ :debug => global_options[:debug])
99
+
100
+ command = ARGV.shift
101
+
102
+ begin
103
+ case command
104
+ when "bucket"
105
+ command_options = Trollop::options do
106
+ banner "manage buckets"
107
+ stop_on BUCKET_SUBCOMMANDS
108
+ end
109
+ subcommand = ARGV.shift
110
+ case subcommand
111
+ when "add"
112
+ subcommand_options = Trollop::options do
113
+ banner "add bucket"
114
+ opt :location, "Location of the bucket - EU or US", :default => "US", :type => :string
115
+ end
116
+ name = ARGV.shift
117
+ Trollop::die "Bucket has not been added because of unknown error" unless create_bucket(service, name, subcommand_options[:location])
118
+ when "remove"
119
+ subcommand_options = Trollop::options do
120
+ banner "remove bucket"
121
+ end
122
+ name = ARGV.shift
123
+ Trollop::die "Bucket name must be given" if name.nil? or name.empty?
124
+ Trollop::die "Bucket has not been removed because of unknown error" unless destroy_bucket(service, name)
125
+ when "show"
126
+ subcommand_options = Trollop::options do
127
+ banner "show bucket"
128
+ opt :prefix, "Limits the response to keys which begin with the indicated prefix", :type => :string
129
+ opt :marker, "Indicates where in the bucket to begin listing", :type => :string
130
+ opt :max_keys, "The maximum number of keys you'd like to see", :type => :integer
131
+ opt :delimiter, "Causes keys that contain the same string between the prefix and the first occurrence of the delimiter to be rolled up into a single result element", :type => :string
132
+ end
133
+ name = ARGV.shift
134
+ Trollop::die "Bucket name must be given" if name.nil? or name.empty?
135
+ show_bucket(service, name, subcommand_options)
136
+ when nil
137
+ list_buckets(service)
138
+ else
139
+ Trollop::die "Unknown subcommand: #{subcommand.inspect}"
140
+ end
141
+ when "object"
142
+ command_options = Trollop::options do
143
+ banner "manage objects"
144
+ stop_on OBJECT_SUBCOMMANDS
145
+ end
146
+ subcommand = ARGV.shift
147
+ case subcommand
148
+ when "add"
149
+ subcommand_options = Trollop::options do
150
+ banner "object add s3_object_name local_file_name"
151
+ opt :type, "A standard MIME type describing the format of the contents", :default => "binary/octet-stream"
152
+ opt :disposition, "Specifies presentational information for the object", :type => :string
153
+ opt :encoding, "Specifies what content encodings have been applied to the object and thus what decoding mechanisms must be applied in order to obtain the media-type referenced by the Content-Type header field", :type => :string
154
+ opt :acl, "The canned ACL to apply to the object. Options include private, public-read, public-read-write, and authenticated-read", :type => :string
155
+ end
156
+ name = ARGV.shift
157
+ Trollop::die "No object name given" if name.nil? or name.empty?
158
+ file_name = ARGV.shift
159
+ Trollop::die "No file name given" if file_name.nil? or file_name.empty?
160
+ Trollop::die "Object has not been added because of unknown error" unless create_object(service, name, file_name, subcommand_options)
161
+ when "remove"
162
+ subcommand_options = Trollop::options do
163
+ banner "object remove s3_object_name"
164
+ end
165
+ name = ARGV.shift
166
+ Trollop::die "No object name given" if name.nil? or name.empty?
167
+ Trollop::die "Object has not been removed because of unknown error" unless destroy_object(service, name)
168
+ when "show"
169
+ subcommand_options = Trollop::options do
170
+ banner "object show s3_object_name optional_file_name"
171
+ end
172
+ name = ARGV.shift
173
+ Trollop::die "No object name given" if name.nil? or name.empty?
174
+ file_name = ARGV.shift
175
+ show_object(service, name, file_name)
176
+ when nil
177
+ list_objects(service)
178
+ else
179
+ Trollop::die "Unknown subcommand: #{subcommand.inspect}"
180
+ end
181
+ when nil
182
+ Trollop::die "No command given"
183
+ else
184
+ Trollop::die "Unknown command #{command.inspect}"
185
+ end
186
+ rescue Error::ResponseError => e
187
+ Trollop::die e.message.sub(/\.+\Z/, "")
188
+ end
data/lib/s3.rb ADDED
@@ -0,0 +1,23 @@
1
+ require "time"
2
+ require "openssl"
3
+ require "net/http"
4
+ require "net/https"
5
+ require "base64"
6
+ require "forwardable"
7
+ require "digest/md5"
8
+
9
+ require "xmlsimple"
10
+
11
+ require "s3/roxy/proxy"
12
+ require "s3/roxy/moxie"
13
+
14
+ require "s3/bucket"
15
+ require "s3/connection"
16
+ require "s3/exceptions"
17
+ require "s3/object"
18
+ require "s3/service"
19
+ require "s3/signature"
20
+
21
+ module S3
22
+ HOST = "s3.amazonaws.com"
23
+ end
data/lib/s3/bucket.rb ADDED
@@ -0,0 +1,154 @@
1
+ module S3
2
+ class Bucket
3
+ extend Roxy::Moxie
4
+ extend Forwardable
5
+
6
+ attr_reader :name, :service
7
+
8
+ def_instance_delegators :service, :service_request
9
+
10
+ def retrieve
11
+ bucket_request(:get, :params => { :max_keys => 0 })
12
+ self
13
+ end
14
+
15
+ def location(reload = false)
16
+ if reload or @location.nil?
17
+ response = bucket_request(:get, :params => { :location => nil })
18
+ @location = parse_location(response.body)
19
+ else
20
+ @location
21
+ end
22
+ end
23
+
24
+ def ==(other)
25
+ self.name == other.name and self.service == other.service
26
+ end
27
+
28
+ def exists?
29
+ retrieve
30
+ true
31
+ rescue Error::NoSuchBucket
32
+ false
33
+ end
34
+
35
+ def destroy
36
+ bucket_request(:delete)
37
+ true
38
+ end
39
+
40
+ def save(location = nil)
41
+ location = location.to_s.upcase if location
42
+ options = { :headers => {} }
43
+ if location and location != "US"
44
+ options[:body] = "<CreateBucketConfiguration><LocationConstraint>#{location}</LocationConstraint></CreateBucketConfiguration>"
45
+ options[:headers][:content_type] = "application/xml"
46
+ end
47
+ bucket_request(:put, options)
48
+ true
49
+ end
50
+
51
+ def vhost?
52
+ "#@name.#{HOST}" =~ /\A#{URI::REGEXP::PATTERN::HOSTNAME}\Z/
53
+ end
54
+
55
+ def host
56
+ vhost? ? "#@name.#{HOST}" : "#{HOST}"
57
+ end
58
+
59
+ def path_prefix
60
+ vhost? ? "" : "#@name/"
61
+ end
62
+
63
+ def objects(reload = false, options = {})
64
+ if options.empty?
65
+ if reload or @objects.nil?
66
+ @objects = fetch_objects
67
+ else
68
+ @objects
69
+ end
70
+ else
71
+ fetch_objects(options)
72
+ end
73
+ end
74
+
75
+ proxy :objects do
76
+ def build(key)
77
+ Object.new(proxy_owner, key)
78
+ end
79
+
80
+ def find_first(name)
81
+ object = build(name)
82
+ object.retrieve
83
+ end
84
+ alias :find :find_first
85
+
86
+ def find_all(options = {})
87
+ proxy_owner.objects(true, options)
88
+ end
89
+
90
+ def reload
91
+ proxy_owner.objects(true)
92
+ end
93
+
94
+ def destroy_all
95
+ proxy_target.each do |object|
96
+ object.destroy
97
+ end
98
+ end
99
+ end
100
+
101
+ def inspect
102
+ "#<#{self.class}:#{name}>"
103
+ end
104
+
105
+ def initialize(service, name)
106
+ self.service = service
107
+ self.name = name
108
+ end
109
+
110
+ private
111
+
112
+ attr_writer :service
113
+
114
+ def name=(name)
115
+ raise ArgumentError.new("Invalid bucket name: #{name}") unless name_valid?(name)
116
+ @name = name
117
+ end
118
+
119
+ def bucket_request(method, options = {})
120
+ path = "#{path_prefix}#{options[:path]}"
121
+ service_request(method, options.merge(:host => host, :path => path))
122
+ end
123
+
124
+ def fetch_objects(options = {})
125
+ response = bucket_request(:get, options)
126
+ parse_objects(response.body)
127
+ end
128
+
129
+ def parse_objects(xml_body)
130
+ xml = XmlSimple.xml_in(xml_body)
131
+ objects_attributes = xml["Contents"]
132
+ if objects_attributes
133
+ objects_attributes.map do |object_attributes|
134
+ Object.new(self,
135
+ object_attributes["Key"].first,
136
+ :etag => object_attributes["ETag"].first,
137
+ :last_modified => object_attributes["LastModified"].first,
138
+ :size => object_attributes["Size"].first)
139
+ end
140
+ else
141
+ []
142
+ end
143
+ end
144
+
145
+ def parse_location(xml_body)
146
+ xml = XmlSimple.xml_in(xml_body)
147
+ xml["content"]
148
+ end
149
+
150
+ def name_valid?(name)
151
+ name =~ /\A[a-z0-9][a-z0-9\._-]{2,254}\Z/ and @name !~ /\A#{URI::REGEXP::PATTERN::IPV4ADDR}\Z/
152
+ end
153
+ end
154
+ end