s3ranger 0.1.0

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/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/s3ranger ADDED
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
4
+
5
+ require "s3ranger/exceptions"
6
+ require "s3ranger/config"
7
+ require "s3ranger/cmd"
8
+
9
+ conf = S3Ranger::Config.new
10
+
11
+ # Time to load config and see if we've got everything we need to cook our salad
12
+ begin
13
+ conf.read
14
+ rescue S3Ranger::NoConfigFound => exc
15
+ # We can't proceed without having those two vars set
16
+ if not (conf.has_key? "AWS_ACCESS_KEY_ID" and conf.has_key? "AWS_SECRET_ACCESS_KEY")
17
+ $stderr.puts "You didn't set up your environment variables :("
18
+ $stderr.puts "I tried the following paths:"
19
+ exc.paths_checked.each {|path| $stderr.puts " * #{path}/s3config.yml"}
20
+
21
+ $stderr.puts "You could try to set the `S3CONF' environment variable."
22
+ $stderr.puts "Learn how to do that here: https://github.com/clarete/s3ranger"
23
+ exit
24
+ end
25
+ end
26
+
27
+
28
+ # Step aside, the star of this show is here. Let's try to create the
29
+ # environment to run the requested command. And feed the user back if
30
+ # information needed was not enough
31
+ begin
32
+ S3Ranger::Cmd.new(conf)
33
+
34
+ rescue S3Ranger::FailureFeedback => exc
35
+ $stderr.puts exc.message
36
+ exit 1
37
+
38
+ rescue S3Ranger::WrongUsage => exc
39
+ name = $0.split('/').last
40
+
41
+ $stderr.puts <<"ENDUSAGE"
42
+ Usage: #{name} [options] <command> [arg(s)]
43
+
44
+ Global Options:
45
+ -h, --help
46
+ -v, --verbose
47
+ -n, --dryrun
48
+ -d, --debug
49
+ --progress
50
+
51
+ #{name} listbuckets
52
+ List all available buckets
53
+
54
+ #{name} createbucket <bucket>
55
+ Creates a new bucket
56
+
57
+ Options:
58
+
59
+ -a <ACL>, --acl=(private|public_read|public_read_write)
60
+
61
+ #{name} deletebucket [options] <bucket>
62
+ Removes an existing bucket
63
+
64
+ Options:
65
+ -f, --force Deletes non-empty buckets (BE CAREFUL)
66
+
67
+ #{name} list <bucket>[:prefix]
68
+ List content inside of bucket
69
+
70
+ if `prefix' is present, only content under `prefix' will be listed.
71
+
72
+ #{name} delete <bucket>:<key>
73
+ Removes `key` from `bucket`
74
+
75
+ #{name} url [options] <bucket>:<key>
76
+ Generates a presigned URL for an operation on the object named `key' found on
77
+ `bucket'.
78
+
79
+ Options:
80
+ --no-ssl
81
+ --expires-in=(<# of seconds> | [#d|#h|#m|#s])
82
+
83
+ #{name} get <bucket>:<key> <file>
84
+ Retrieves the remote `key` object from `bucket` and saves to the local path
85
+ specified in `file`
86
+
87
+ #{name} put <bucket>[:<key>] <file>
88
+ Uploads the file `file` to the `bucket` under the path `key`
89
+
90
+ #{name} sync <source> <destination>
91
+
92
+ One of source or destination must be remote and the other must be local,
93
+ where local points to a folder in the file system and remote conform to the
94
+ format `<bucket>[:<key>]`.
95
+
96
+ Options:
97
+ -e <pattern>, --exclude=<pattern>
98
+ -k, --keep
99
+ -d, --dry-run
100
+
101
+ ENDUSAGE
102
+
103
+ $stderr.puts "\nCurrent error:\n #{exc.msg}\n" if exc.msg
104
+ exit exc.error_code
105
+ end
@@ -0,0 +1,112 @@
1
+ # (c) 2013 Lincoln de Sousa <lincoln@clarete.li>
2
+ # (c) 2007 s3sync.net
3
+ #
4
+ # This software code is made available "AS IS" without warranties of any
5
+ # kind. You may copy, display, modify and redistribute the software
6
+ # code either by itself or as incorporated into your code; provided that
7
+ # you do not remove any proprietary notices. Your use of this software
8
+ # code is at your own risk and you waive any claim against the author
9
+ # with respect to your use of this software code.
10
+
11
+ require 'getoptlong'
12
+ require 's3ranger/commands'
13
+ require 's3ranger/util'
14
+
15
+ module S3Ranger
16
+
17
+ class Cmd
18
+
19
+ def initialize(conf = conf)
20
+ # The chain that initializes our command and find the right action
21
+ options, command, bucket, key, file = read_info_from_args(parse_args())
22
+
23
+ # Finding the right command to run
24
+ (cmd = find_cmd(command)) || (raise WrongUsage.new(nil, "Command `#{command}' does not exist"))
25
+
26
+ # Now that we're sure we have things to do, we need to connect to amazon
27
+ s3 = AWS::S3.new(
28
+ :access_key_id => conf[:AWS_ACCESS_KEY_ID],
29
+ :secret_access_key => conf[:AWS_SECRET_ACCESS_KEY],
30
+ )
31
+
32
+ # Calling the actuall command
33
+ cmd.call({
34
+ :options => options,
35
+ :s3 => s3,
36
+ :bucket => bucket,
37
+ :key => key,
38
+ :file => file,
39
+ })
40
+ end
41
+
42
+ def find_cmd name
43
+ sym = "_cmd_#{name}".to_sym
44
+ return nil unless Commands.public_methods.include? sym
45
+ return Commands.method sym
46
+ end
47
+
48
+ def parse_args
49
+ options = Hash.new
50
+
51
+ args = [
52
+ ['--help', '-h', GetoptLong::NO_ARGUMENT],
53
+ ['--force', '-f', GetoptLong::NO_ARGUMENT],
54
+ ['--acl', '-a', GetoptLong::REQUIRED_ARGUMENT],
55
+ ['--method', '-m', GetoptLong::REQUIRED_ARGUMENT],
56
+ ['--exclude', '-e', GetoptLong::REQUIRED_ARGUMENT],
57
+ ['--keep', '-k', GetoptLong::NO_ARGUMENT],
58
+ ['--dry-run', '-d', GetoptLong::NO_ARGUMENT],
59
+ ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
60
+ ['--no-ssl', GetoptLong::NO_ARGUMENT],
61
+ ['--expires-in', GetoptLong::REQUIRED_ARGUMENT],
62
+ ]
63
+
64
+ begin
65
+ GetoptLong.new(*args).each {|opt, arg| options[opt] = (arg || true)}
66
+ rescue StandardError => exc
67
+ raise WrongUsage.new nil, exc.message
68
+ end
69
+
70
+ # Let's just show the help to the user
71
+ raise WrongUsage.new(0, nil) if options['--help']
72
+
73
+ # Returning the options to the next level
74
+ options
75
+ end
76
+
77
+ def read_info_from_args(options)
78
+ # Parsing expre date
79
+ if options['--expires-in'] =~ /d|h|m|s/
80
+
81
+ val = 0
82
+
83
+ options['--expires-in'].scan(/(\d+\w)/) do |track|
84
+ _, num, what = /(\d+)(\w)/.match(track[0]).to_a
85
+ num = num.to_i
86
+
87
+ case what
88
+ when "d"; val += num * 86400
89
+ when "h"; val += num * 3600
90
+ when "m"; val += num * 60
91
+ when "s"; val += num
92
+ end
93
+ end
94
+
95
+ options['--expires-in'] = val
96
+ end
97
+
98
+ # Reading what to do from the user
99
+ command = ARGV.shift
100
+ raise WrongUsage.new(nil, "Need a command (eg.: `list', `listbuckets', etc)") if not command
101
+
102
+ key, file = ARGV
103
+
104
+ # Parsing the bucket name
105
+ bucket = nil
106
+ bucket, key = key.split(':') if key
107
+
108
+ # Returning things we need in the next level
109
+ [options, command, bucket, key, file]
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,114 @@
1
+ require 's3ranger/exceptions'
2
+ require 's3ranger/sync'
3
+ require 'aws/s3'
4
+
5
+
6
+ module Commands
7
+
8
+ include S3Ranger
9
+
10
+ AVAILABLE_ACLS = [:public_read, :public_read_write, :private]
11
+
12
+ AVAILABLE_METHODS = ['read', 'get', 'put', 'write', 'delete']
13
+
14
+ def Commands._cmd_listbuckets args
15
+ args[:s3].buckets.each do |bkt|
16
+ puts "#{bkt.name}"
17
+ end
18
+ end
19
+
20
+ def Commands._cmd_createbucket args
21
+ raise WrongUsage.new(nil, "You need to inform a bucket") if not args[:bucket]
22
+
23
+ begin
24
+ params = {}
25
+ if acl = args[:options]['--acl']
26
+ raise WrongUsage.new(nil, "Invalid ACL. Should be any of #{EXISTING_ACLS.join ', '}") if not AVAILABLE_ACLS.include? acl
27
+ params.merge!({:acl => acl.to_sym})
28
+ end
29
+
30
+ args[:s3].buckets.create args[:bucket], params
31
+ rescue AWS::S3::Errors::BucketAlreadyExists => exc
32
+ raise FailureFeedback.new("Bucket `#{bucket}' already exists")
33
+ end
34
+ end
35
+
36
+ def Commands._cmd_deletebucket args
37
+ raise WrongUsage.new(nil, "You need to inform a bucket") if not args[:bucket]
38
+
39
+ # Getting the bucket
40
+ bucket_obj = args[:s3].buckets[args[:bucket]]
41
+
42
+ # Do not kill buckets with content unless explicitly asked
43
+ if not args[:options]['--force'] and bucket_obj.objects.count > 0
44
+ raise FailureFeedback.new("Cowardly refusing to remove non-empty bucket `#{bucket}'. Try with -f.")
45
+ end
46
+
47
+ begin
48
+ bucket_obj.delete!
49
+ rescue AWS::S3::Errors::AccessDenied => exc
50
+ raise FailureFeedback.new("Access Denied")
51
+ end
52
+ end
53
+
54
+ def Commands._cmd_list args
55
+ raise WrongUsage.new(nil, "You need to inform a bucket") if not args[:bucket]
56
+ args[:s3].buckets[args[:bucket]].objects.with_prefix(args[:key] || "").each do |object|
57
+ puts "#{object.key}\t#{object.content_length}\t#{object.last_modified}"
58
+ end
59
+ end
60
+
61
+ def Commands._cmd_delete args
62
+ raise WrongUsage.new(nil, "You need to inform a bucket") if not args[:bucket]
63
+ raise WrongUsage.new(nil, "You need to inform a key") if not args[:key]
64
+ args[:s3].buckets[args[:bucket]].objects[args[:key]].delete
65
+ end
66
+
67
+ def Commands._cmd_url args
68
+ raise WrongUsage.new(nil, "You need to inform a bucket") if not args[:bucket]
69
+ raise WrongUsage.new(nil, "You need to inform a key") if not args[:key]
70
+
71
+ method = args[:options]['--method'] || 'read'
72
+ raise WrongUsage.new(nil, "") unless AVAILABLE_METHODS.include? method
73
+
74
+ opts = {}
75
+ opts.merge!({:secure => args[:options]["--no-ssl"].nil?})
76
+ opts.merge!({:expires => args[:options]["--expires-in"]}) if args[:options]["--expires-in"]
77
+ p (args[:s3].buckets[args[:bucket]].objects[args[:key]].url_for method.to_sym, opts).to_s
78
+ end
79
+
80
+ def Commands._cmd_put args
81
+ raise WrongUsage.new(nil, "You need to inform a bucket") if not args[:bucket]
82
+ raise WrongUsage.new(nil, "You need to inform a file") if not args[:file]
83
+
84
+ # key + file name
85
+ name = S3Ranger.safe_join [args[:key], File.basename(args[:file])]
86
+ args[:s3].buckets[args[:bucket]].objects[name].write Pathname.new(args[:file])
87
+ end
88
+
89
+ def Commands._cmd_get args
90
+ raise WrongUsage.new(nil, "You need to inform a bucket") if not args[:bucket]
91
+ raise WrongUsage.new(nil, "You need to inform a key") if not args[:key]
92
+ raise WrongUsage.new(nil, "You need to inform a file") if not args[:file]
93
+
94
+ # Saving the content to be downloaded to the current directory if the
95
+ # destination is a directory
96
+ path = File.absolute_path args[:file]
97
+ path = S3Ranger.safe_join [path, File.basename(args[:key])] if File.directory? path
98
+
99
+ File.open(path, 'wb') do |f|
100
+ begin
101
+ args[:s3].buckets[args[:bucket]].objects[args[:key]].read do |chunk| f.write(chunk) end
102
+ rescue AWS::S3::Errors::NoSuchBucket
103
+ raise FailureFeedback.new("There's no bucket named `#{args[:bucket]}'")
104
+ rescue AWS::S3::Errors::NoSuchKey
105
+ raise FailureFeedback.new("There's no key named `#{args[:key]}' in the bucket `#{args[:bucket]}'")
106
+ end
107
+ end
108
+ end
109
+
110
+ def Commands._cmd_sync args
111
+ cmd = SyncCommand.new args, *ARGV
112
+ cmd.run
113
+ end
114
+ end
@@ -0,0 +1,43 @@
1
+ # This software code is made available "AS IS" without warranties of any
2
+ # kind. You may copy, display, modify and redistribute the software
3
+ # code either by itself or as incorporated into your code; provided that
4
+ # you do not remove any proprietary notices. Your use of this software
5
+ # code is at your own risk and you waive any claim against the author
6
+ # with respect to your use of this software code.
7
+ # (c) 2007 alastair brunton
8
+ #
9
+ # modified to search out the yaml in several places, thanks wkharold.
10
+
11
+ require 'yaml'
12
+ require 's3ranger/exceptions'
13
+
14
+
15
+ module S3Ranger
16
+
17
+ class Config < Hash
18
+ def read
19
+ paths_checked = []
20
+
21
+ ["#{ENV['S3CONF']}", "#{ENV['HOME']}/.s3conf", "/etc/s3conf"].each do |path|
22
+
23
+ # Filtering some garbage
24
+ next if path.nil? or path.strip.empty?
25
+
26
+ # Feeding the user feedback in case of failure
27
+ paths_checked << path
28
+
29
+ # Time for the dirty work, let's parse the config file and feed our
30
+ # internal hash
31
+ if File.exists?("#{path}/s3config.yml")
32
+ config = YAML.load_file("#{path}/s3config.yml")
33
+ config.each_pair do |key, value|
34
+ self[key.upcase.to_sym] = value
35
+ end
36
+ return
37
+ end
38
+ end
39
+
40
+ raise NoConfigFound.new paths_checked
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,38 @@
1
+ # (c) 2013 Lincoln de Sousa <lincoln@clarete.li>
2
+ #
3
+ # This software code is made available "AS IS" without warranties of any
4
+ # kind. You may copy, display, modify and redistribute the software
5
+ # code either by itself or as incorporated into your code; provided that
6
+ # you do not remove any proprietary notices. Your use of this software
7
+ # code is at your own risk and you waive any claim against the author
8
+ # with respect to your use of this software code.
9
+
10
+ module S3Ranger
11
+
12
+ class SyncException < StandardError
13
+ end
14
+
15
+ class NoConfigFound < SyncException
16
+
17
+ attr_accessor :paths_checked
18
+
19
+ def initialize(paths_checked)
20
+ @paths_checked = paths_checked
21
+ end
22
+ end
23
+
24
+ class WrongUsage < SyncException
25
+
26
+ attr_accessor :error_code
27
+ attr_accessor :msg
28
+
29
+ def initialize(error_code, msg)
30
+ @error_code = error_code || 1
31
+ @msg = msg
32
+ end
33
+ end
34
+
35
+ class FailureFeedback < SyncException
36
+ end
37
+
38
+ end