s3cp 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ === 0.1 / 2011-04-05
2
+
3
+ * First release
4
+
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ S3CP: Commands-line tools for Amazon S3 file manipulation
2
+ =============================================================
3
+
4
+ Just a few simple command-line utilities to list, copy, view S3 files, e.g., `s3cp`, `s3ls`, `s3cat`.
5
+
6
+ ### Building ###
7
+
8
+ # rake gem
9
+ # gem install s3cp-0.1.0.gem
10
+
11
+ ### Examples ###
12
+
13
+ export AWS_ACCESS_KEY_ID=...
14
+ export AWS_SECRET_ACCESS_KEY=...
15
+
16
+ s3ls s3://mybucket/path/to/some/files
17
+ s3cat s3://mybucket/path/to/some/file.txt
18
+ s3cp local_file.bin s3://mybucket/some/path
19
+ s3mod s3://mybucket/path/to/some/file.txt public-read
20
+
21
+ Use the `-h` option to learn about command-line options.
22
+
23
+ All commands support both `s3://bucket/path/to/file` and the legacy `bucket:path/to/file` syntax.
24
+
25
+ Commands are also TTY-aware; when run in an interactive shell, their behavior will change. For example, `s3cat` will launch your favorite `PAGER` or `less` (the default pager) whereas `s3ls` will display N items at a time, where N is the number of display lines on your terminal and pause between pages.
26
+
27
+ ### Dependencies ###
28
+
29
+ * highline `>=1.5.1` (console/terminal size guessing)
30
+ * right_aws `=2.1.0` (underlying Amazon S3 API)
31
+ * right_http_connection `=1.3.0` (required by `right_aws`)
32
+
33
+ ### Target platform ###
34
+
35
+ * Ruby 1.8.7 / 1.9.2
36
+
37
+ ### License ###
38
+
39
+ S3CP is is licensed under the terms of the Apache Software License v2.0.
40
+ <http://www.apache.org/licenses/LICENSE-2.0.html>
41
+
data/bin/s3cat ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 's3cp/s3cat'
3
+
data/bin/s3cp ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 's3cp/s3cp'
3
+
data/bin/s3ls ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 's3cp/s3ls'
3
+
data/bin/s3mod ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 's3cp/s3mod'
3
+
4
+
data/lib/s3cp/s3cat.rb ADDED
@@ -0,0 +1,68 @@
1
+ require 'rubygems'
2
+ require 'right_aws'
3
+ require 'optparse'
4
+ require 'date'
5
+ require 'highline/import'
6
+ require 'tempfile'
7
+
8
+ require 's3cp/utils'
9
+
10
+ # Parse arguments
11
+ options = {}
12
+ options[:tty] = $stdout.isatty
13
+
14
+ op = OptionParser.new do |opts|
15
+ opts.banner = "s3cat [path]"
16
+ opts.separator ''
17
+
18
+ opts.on("--debug", "Debug mode") do
19
+ options[:debug] = true
20
+ end
21
+
22
+ opts.on("--tty", "TTY mode") do |tty|
23
+ options[:tty] = tty
24
+ end
25
+
26
+ opts.on_tail("-h", "--help", "Show this message") do
27
+ puts op
28
+ exit
29
+ end
30
+ end
31
+ op.parse!(ARGV)
32
+
33
+ unless ARGV.size > 0
34
+ puts op
35
+ exit
36
+ end
37
+
38
+ url = ARGV[0]
39
+
40
+ if options[:debug]
41
+ puts "URL: #{url}"
42
+ puts "Options: \n#{options.inspect}"
43
+ end
44
+
45
+ @bucket, @prefix = S3CP.bucket_and_key(url)
46
+ fail "Your URL looks funny, doesn't it?" unless @bucket
47
+
48
+ @s3 = S3CP.connect().interface
49
+
50
+ if options[:tty]
51
+ # store contents to file to display with PAGER
52
+ file = Tempfile.new('s3cat')
53
+ out = File.new(file.path, File::CREAT|File::RDWR)
54
+ begin
55
+ @s3.get(@bucket, @prefix) do |chunk|
56
+ out.write(chunk)
57
+ end
58
+ ensure
59
+ out.close()
60
+ end
61
+ exec "#{ENV['PAGER'] || 'less'} #{file.path}"
62
+ file.delete()
63
+ else
64
+ @s3.get(@bucket, @prefix) do |chunk|
65
+ STDOUT.print(chunk)
66
+ end
67
+ end
68
+
data/lib/s3cp/s3cp.rb ADDED
@@ -0,0 +1,171 @@
1
+ require 'rubygems'
2
+ require 'extensions/kernel' if RUBY_VERSION =~ /1.8/
3
+ require 'right_aws'
4
+ require 'optparse'
5
+ require 'date'
6
+ require 'highline/import'
7
+ require 'fileutils'
8
+
9
+ require 's3cp/utils'
10
+
11
+ # Parse arguments
12
+ options = {}
13
+ options[:verbose] = true if $stdout.isatty
14
+
15
+ op = OptionParser.new do |opts|
16
+ opts.banner = "s3cp [path]"
17
+ opts.separator ''
18
+
19
+ opts.on("-r", "Recursive mode") do
20
+ options[:recursive] = true
21
+ end
22
+
23
+ opts.on("--verbose", "Verbose mode") do
24
+ options[:verbose] = true
25
+ end
26
+
27
+ opts.on("--debug", "Debug mode") do
28
+ options[:debug] = true
29
+ end
30
+
31
+ opts.on_tail("-h", "--help", "Show this message") do
32
+ puts op
33
+ exit
34
+ end
35
+ end
36
+ op.parse!(ARGV)
37
+
38
+ if ARGV.size < 2
39
+ puts op
40
+ exit
41
+ end
42
+
43
+ destination = ARGV.last
44
+ sources = ARGV[0..-2]
45
+
46
+ def s3?(url)
47
+ S3CP.bucket_and_key(url)[0]
48
+ end
49
+
50
+ if options[:debug]
51
+ puts "URL: #{url}"
52
+ puts "Options: \n#{options.inspect}"
53
+ end
54
+
55
+ @bucket = $1
56
+ @prefix = $2
57
+
58
+ @s3 = S3CP.connect()
59
+
60
+ def direction(from, to)
61
+ if s3?(from) && s3?(to)
62
+ :s3_to_s3
63
+ elsif !s3?(from) && s3?(to)
64
+ :local_to_s3
65
+ elsif s3?(from) && !s3?(to)
66
+ :s3_to_local
67
+ else
68
+ :local_to_local
69
+ end
70
+ end
71
+
72
+ def no_slash(path)
73
+ path = path.match(/\/$/) ? path[0..-2] : path
74
+ path.match(/^\//) ? path[1..-1] : path
75
+ end
76
+
77
+ def relative(base, path)
78
+ no_slash(path[base.length..-1])
79
+ end
80
+
81
+ def copy(from, to, options)
82
+ bucket_from, key_from = S3CP.bucket_and_key(from)
83
+ bucket_to, key_to = S3CP.bucket_and_key(to)
84
+
85
+ #puts "bucket_from #{bucket_from.inspect} key_from #{key_from.inspect}"
86
+ #puts "bucket_to #{bucket_to.inspect} key_from #{key_to.inspect}"
87
+ #puts "direction #{direction(from, to)}"
88
+
89
+ case direction(from, to)
90
+ when :s3_to_s3
91
+ if options[:recursive]
92
+ keys = []
93
+ @s3.interface.incrementally_list_bucket(bucket_from, :prefix => key_from) do |page|
94
+ page[:contents].each { |entry| keys << entry[:key] }
95
+ end
96
+ keys.each do |key|
97
+ dest = no_slash(key_to) + '/' + relative(key_from, key)
98
+ puts "Copy s3://#{bucket_from}/#{key} to s3://#{bucket_to}/#{dest}"
99
+ @s3.interface.copy(bucket_from, key, bucket_to, dest)
100
+ end
101
+ else
102
+ puts "Copy s3://#{bucket_from}/#{key_from} to s3://#{bucket_to}/#{key_to}"
103
+ @s3.interface.copy(bucket_from, key_from, bucket_to, key_to)
104
+ end
105
+ when :local_to_s3
106
+ if options[:recursive]
107
+ files = Dir[from + "/**/*"]
108
+ files.each do |f|
109
+ f = File.expand_path(f)
110
+ key = no_slash(key_to) + '/' + relative(from, f)
111
+ puts "Copy #{f} to s3://#{bucket_to}/#{key}"
112
+ @s3.interface.put(bucket_to, key, File.open(f))
113
+ end
114
+ else
115
+ f = File.expand_path(from)
116
+ puts "Copy #{f} to s3://#{bucket_to}/#{key_to}"
117
+ f = File.open(f)
118
+ begin
119
+ @s3.interface.put(bucket_to, key_to, f)
120
+ ensure
121
+ f.close()
122
+ end
123
+ end
124
+ when :s3_to_local
125
+ if options[:recursive]
126
+ keys = []
127
+ @s3.interface.incrementally_list_bucket(bucket_from, :prefix => key_from) do |page|
128
+ page[:contents].each { |entry| keys << entry[:key] }
129
+ end
130
+ keys.each do |key|
131
+ dest = File.expand_path(to) + '/' + relative(key_from, key)
132
+ dest = File.join(dest, File.basename(key)) if File.directory?(dest)
133
+ puts "Copy s3://#{bucket_from}/#{key} to #{dest}"
134
+ dir = File.dirname(dest)
135
+ FileUtils.mkdir_p dir unless File.exist? dir
136
+ fail "Destination path is not a directory: #{dir}" unless File.directory?(dir)
137
+ f = File.new(dest, File::CREAT|File::RDWR)
138
+ begin
139
+ @s3.interface.get(bucket_from, key) do |chunk|
140
+ f.write(chunk)
141
+ end
142
+ ensure
143
+ f.close()
144
+ end
145
+ end
146
+ else
147
+ dest = File.expand_path(to)
148
+ dest = File.join(dest, File.basename(key_from)) if File.directory?(dest)
149
+ puts "Copy s3://#{bucket_from}/#{key_from} to #{dest}"
150
+ f = File.new(dest, File::CREAT|File::RDWR)
151
+ begin
152
+ @s3.interface.get(bucket_from, key_from) do |chunk|
153
+ f.write(chunk)
154
+ end
155
+ ensure
156
+ f.close()
157
+ end
158
+ end
159
+ when :local_to_local
160
+ if options[:recursive]
161
+ FileUtils.cp_r from, to
162
+ else
163
+ FileUtils.cp from, to
164
+ end
165
+ end
166
+ end
167
+
168
+ sources.each do |source|
169
+ copy(source, destination, options)
170
+ end
171
+
data/lib/s3cp/s3ls.rb ADDED
@@ -0,0 +1,84 @@
1
+ require 'rubygems'
2
+ require 'extensions/kernel' if RUBY_VERSION =~ /1.8/
3
+ require 'right_aws'
4
+ require 'optparse'
5
+ require 'date'
6
+ require 'highline/import'
7
+
8
+ require 's3cp/utils'
9
+
10
+ # Parse arguments
11
+ options = {}
12
+ options[:date_format] = '%x %X'
13
+ options[:rows_per_page] = $terminal.output_rows if $stdout.isatty
14
+
15
+ op = OptionParser.new do |opts|
16
+ opts.banner = "s3ls [path]"
17
+ opts.separator ''
18
+
19
+ opts.on("-l", "Long listing format") do
20
+ options[:long_format] = true
21
+ end
22
+
23
+ opts.on("--date-format FORMAT", "Date format (see http://strfti.me/)") do |custom|
24
+ options[:custom_params] = custom
25
+ end
26
+
27
+ opts.on("--verbose", "Verbose mode") do
28
+ options[:verbose] = true
29
+ end
30
+
31
+ opts.on("--rows ROWS", "Rows per page") do |rows|
32
+ options[:rows_per_page] = rows.to_i
33
+ end
34
+
35
+ opts.on_tail("-h", "--help", "Show this message") do
36
+ puts op
37
+ exit
38
+ end
39
+ end
40
+ op.parse!(ARGV)
41
+
42
+ unless ARGV.size > 0
43
+ puts op
44
+ exit
45
+ end
46
+
47
+ url = ARGV[0]
48
+
49
+ if options[:verbose]
50
+ puts "URL: #{url}"
51
+ puts "Options: #{options.inspect}"
52
+ end
53
+
54
+ @bucket, @key = S3CP.bucket_and_key(url)
55
+ fail "Your URL looks funny, doesn't it?" unless @bucket
56
+
57
+ if options[:verbose]
58
+ puts "bucket #{@bucket}"
59
+ puts "key #{@key}"
60
+ end
61
+
62
+ @s3 = S3CP.connect()
63
+
64
+ rows = 0
65
+ @s3.interface.incrementally_list_bucket(@bucket, :prefix => @key) do |page|
66
+ page[:contents].each do |entry|
67
+ key = entry[:key]
68
+ last_modified = DateTime.parse(entry[:last_modified])
69
+ if options[:long_format]
70
+ puts "#{last_modified.strftime(options[:date_format])} #{key}"
71
+ else
72
+ puts key
73
+ end
74
+ rows += 1
75
+ if options[:rows_per_page] && (rows % options[:rows_per_page] == 0)
76
+ begin
77
+ print "Continue? (Y/n) "
78
+ response = STDIN.gets.chomp.downcase
79
+ end until response == 'n' || response == 'y' || response == ''
80
+ exit if response == 'n'
81
+ end
82
+ end
83
+ end
84
+
data/lib/s3cp/s3mod.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require 'right_aws'
3
+ require 'optparse'
4
+ require 's3cp/utils'
5
+
6
+ op = OptionParser.new do |opts|
7
+ opts.banner = "s3mod [path] [permission]"
8
+
9
+ opts.on_tail("-h", "--help", "Show this message") do
10
+ puts op
11
+ exit
12
+ end
13
+ end
14
+
15
+ op.parse!(ARGV)
16
+
17
+ if ARGV.size < 2
18
+ puts op
19
+ exit
20
+ end
21
+
22
+ def update_permissions(s3, bucket, key, permission)
23
+ puts "Setting #{permission} on s3://#{bucket}/#{key}"
24
+ s3.interface.copy(bucket, key, bucket, key, :replace, {"x-amz-acl" => permission })
25
+ end
26
+
27
+ source = ARGV[0]
28
+ permission = ARGV.last
29
+
30
+ LEGAL_MODS = %w{private authenticated-read public-read public-read-write}
31
+ raise "Permissions must be one of the following values: #{LEGAL_MODS}" unless LEGAL_MODS.include?(permission)
32
+
33
+ @s3 = S3CP.connect()
34
+ bucket,key = S3CP.bucket_and_key(source)
35
+ update_permissions(@s3, bucket, key, permission)
data/lib/s3cp/utils.rb ADDED
@@ -0,0 +1,28 @@
1
+ module S3CP
2
+ extend self
3
+
4
+ # Connect to AWS S3
5
+ def connect()
6
+ access_key = ENV["AWS_ACCESS_KEY_ID"] || raise("Missing environment variable AWS_ACCESS_KEY_ID")
7
+ secret_key = ENV["AWS_SECRET_ACCESS_KEY"] || raise("Missing environment variable AWS_SECRET_ACCESS_KEY")
8
+
9
+ logger = Logger.new('/dev/null')
10
+ RightAws::S3.new(access_key, secret_key, :logger => logger)
11
+ end
12
+
13
+ # Parse URL and return bucket and key.
14
+ #
15
+ # e.g. s3://bucket/path/to/key => ["bucket", "path/to/key"]
16
+ # bucket:path/to/key => ["bucket", "path/to/key"]
17
+ def bucket_and_key(url)
18
+ if url =~ /s3:\/\/([^\/]+)\/?(.*)/
19
+ bucket = $1
20
+ key = $2
21
+ elsif url =~ /([^:]+):(.*)/
22
+ bucket = $1
23
+ key = $2
24
+ end
25
+ [bucket, key]
26
+ end
27
+ end
28
+
@@ -0,0 +1,4 @@
1
+
2
+ module S3CP
3
+ VERSION = "0.1.1"
4
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: s3cp
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 1
10
+ version: 0.1.1
11
+ platform: ruby
12
+ authors:
13
+ - Alex Boisvert
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-09-22 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ version_requirements: &id001 !ruby/object:Gem::Requirement
22
+ none: false
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ hash: 7
27
+ segments:
28
+ - 0
29
+ - 6
30
+ version: "0.6"
31
+ requirement: *id001
32
+ type: :runtime
33
+ name: extensions
34
+ prerelease: false
35
+ - !ruby/object:Gem::Dependency
36
+ version_requirements: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ hash: 1
42
+ segments:
43
+ - 1
44
+ - 5
45
+ - 1
46
+ version: 1.5.1
47
+ requirement: *id002
48
+ type: :runtime
49
+ name: highline
50
+ prerelease: false
51
+ - !ruby/object:Gem::Dependency
52
+ version_requirements: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ hash: 11
58
+ segments:
59
+ - 2
60
+ - 1
61
+ - 0
62
+ version: 2.1.0
63
+ requirement: *id003
64
+ type: :runtime
65
+ name: right_aws
66
+ prerelease: false
67
+ - !ruby/object:Gem::Dependency
68
+ version_requirements: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ hash: 27
74
+ segments:
75
+ - 1
76
+ - 3
77
+ - 0
78
+ version: 1.3.0
79
+ requirement: *id004
80
+ type: :runtime
81
+ name: right_http_connection
82
+ prerelease: false
83
+ - !ruby/object:Gem::Dependency
84
+ version_requirements: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ hash: 27
90
+ segments:
91
+ - 2
92
+ - 5
93
+ - 0
94
+ version: 2.5.0
95
+ requirement: *id005
96
+ type: :development
97
+ name: rspec
98
+ prerelease: false
99
+ - !ruby/object:Gem::Dependency
100
+ version_requirements: &id006 !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ~>
104
+ - !ruby/object:Gem::Version
105
+ hash: 49
106
+ segments:
107
+ - 0
108
+ - 8
109
+ - 7
110
+ version: 0.8.7
111
+ requirement: *id006
112
+ type: :development
113
+ name: rake
114
+ prerelease: false
115
+ description:
116
+ email:
117
+ - alex.boisvert@gmail.com
118
+ executables:
119
+ - s3ls
120
+ - s3cp
121
+ - s3cat
122
+ - s3mod
123
+ extensions: []
124
+
125
+ extra_rdoc_files:
126
+ - README.md
127
+ - History.txt
128
+ files:
129
+ - lib/s3cp/s3ls.rb
130
+ - lib/s3cp/s3mod.rb
131
+ - lib/s3cp/s3cp.rb
132
+ - lib/s3cp/version.rb
133
+ - lib/s3cp/utils.rb
134
+ - lib/s3cp/s3cat.rb
135
+ - History.txt
136
+ - README.md
137
+ - bin/s3cp
138
+ - bin/s3cat
139
+ - bin/s3ls
140
+ - bin/s3mod
141
+ homepage:
142
+ licenses: []
143
+
144
+ post_install_message:
145
+ rdoc_options: []
146
+
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ none: false
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ hash: 3
155
+ segments:
156
+ - 0
157
+ version: "0"
158
+ required_rubygems_version: !ruby/object:Gem::Requirement
159
+ none: false
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ hash: 23
164
+ segments:
165
+ - 1
166
+ - 3
167
+ - 6
168
+ version: 1.3.6
169
+ requirements: []
170
+
171
+ rubyforge_project:
172
+ rubygems_version: 1.8.10
173
+ signing_key:
174
+ specification_version: 3
175
+ summary: Amazon S3 tools to, e.g., list, copy, delete S3 files
176
+ test_files: []
177
+