#! /usr/bin/env ruby
# filename: bucket
#
# bucket foobar create
# bucket foobar store foo.png
# bucket foobar list
#
require 'rubygems'
require 'aws/s3'
require 'progressbar'
require 'mime/types'
require 'main'
require 'yaml'
Main {
name 'bucket'
description %|
bucket is a command-line interface for amazon's s3 service.
|
Modes = []
def self.mode name, &block
Modes << name.to_s and define_method name, &block
end
argument('name'){
synopsis "the bucket name"
}
argument('mode'){
optional
synopsis "one of #{ Modes.join '|' }"
}
option('access_key_id', 'K'){
default ENV['AMAZON_ACCESS_KEY_ID']||ENV['ACCESS_KEY_ID']
}
option('secret_access_key', 'S'){
default ENV['AMAZON_SECRET_ACCESS_KEY']||ENV['SECRET_ACCESS_KEY']
}
option('namespace', 'n'){
default 'drawohara.com'
}
option('access', 'a'){
default 'public_read'
cast :symbol
}
option('force', 'f'){
cast :bool
}
option('config', 'C'){
argument_required
}
option('content_type', 'c'){
argument_required
}
option('path_info', 'P'){
default false
}
option('dirty', 'D'){
}
parameters.each{|parameter| fattr parameter.name}
def run
setup
establish_connection
send mode
end
def setup
load_config_yml
self.class.fattrs.each{|attr| send attr, params[attr].value if params.has_key?(attr)}
if %r/^[A-Z]/.match(name) or mode.nil?
argv.unshift mode
mode name.downcase.capitalize
name nil
end
abort "bad mode #{ mode }" unless respond_to? mode
end
def load_config_yml
if params['config'].given?
path = params['config'].value
else
path = default_config_path
end
if test(?e, path) or params['config'].given?
config(( YAML.load IO.read(path) ))
config.each{|k,v| send(k, v) rescue nil}
end
end
def default_config_path
home = File.expand_path '~'
path = File.join home, '.bucket.yml'
end
def namespaced arg, *argv
argv.unshift arg and [ namespace, argv ].compact.join('.')
end
def establish_connection
AWS::S3::Base.establish_connection!(
:access_key_id => access_key_id,
:secret_access_key => secret_access_key
)
end
mode :create do
AWS::S3::Bucket.create namespaced(name)
puts "http://s3.amazonaws.com/#{ namespaced(name) }" ### HACKIY HACK
end
mode :store do
path = argv.shift or abort "no path"
basename = File.basename path
key = argv.shift || path
key = File.basename key #unless path_info?
key = clean key unless dirty?
content_type = self.content_type || (MIME::Types.type_for(basename).first.content_type rescue 'text/plain')
options = {
:access => access,
:content_type => content_type,
}
open path, 'r+' do |fd|
progress = ProgressBar.new key, fd.stat.size
t = Thread.new do
Thread.current.abort_on_exception = true
AWS::S3::S3Object.store key, fd, namespaced(name), options
end
### progress.set fd.pos and sleep 0.142 until fd.eof? if STDIN.tty?
t.join
end
puts AWS::S3::S3Object.url_for(key, namespaced(name), :authenticated => false)
end
def clean path
path.strip.downcase.gsub(%r/[^\.\w\d_-]/o, '_').squeeze('_')
end
mode :list do
AWS::S3::Bucket.find(namespaced(name)).objects.each do |object|
puts object.url(:authenticated => false)
end
end
mode :List do
puts AWS::S3::Service.buckets.map{|bucket| bucket.name}
end
mode :delete do
return delete_object unless argv.empty?
options = {
:force => force
}
AWS::S3::Bucket.delete namespaced(name), options
end
mode :delete_object do
key = argv.shift
key = File.basename key unless path_info?
key = clean key unless dirty?
object = AWS::S3::Bucket.find(namespaced(name))[key]
if object
url = object.url :authenticated => false
object.delete
puts url
else
exit 42
end
end
mode :init do
generate_config
end
def generate_config
path = config || default_config_path
unless(not force and test(?e, path))
open path, 'w' do |fd|
config = <<-yml
access_key_id : 'AMAZON_ACCESS_KEY_ID'
secret_access_key : 'AMAZON_SECRET_ACCESS_KEY'
namespace : 'this_will_be_prepended_to_all_keys'
yml
formatted = config.split(%r/\n/).map{|line| line.strip}.join("\n")
fd.puts formatted
end
if STDIN.tty?
editor = ENV['EDITOR'] || ENV['EDIT'] || 'vi'
system "#{ editor } #{ path }"
end
puts "generated #{ path }"
else
warn "exists #{ path }"
exit 42
end
end
}