s3cp 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+