heroku_hatchet 0.0.2 → 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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +14 -0
  3. data/CHANGELOG.md +4 -0
  4. data/README.md +2 -2
  5. data/Rakefile +6 -0
  6. data/hatchet.gemspec +3 -1
  7. data/hatchet.json +3 -2
  8. data/lib/hatchet.rb +2 -0
  9. data/lib/hatchet/anvil_app.rb +28 -18
  10. data/lib/hatchet/app.rb +19 -5
  11. data/lib/hatchet/git_app.rb +5 -2
  12. data/lib/hatchet/tasks.rb +17 -7
  13. data/lib/hatchet/version.rb +1 -1
  14. data/test/fixtures/buildpacks/heroku-buildpack-ruby/.gitignore +4 -0
  15. data/test/fixtures/buildpacks/heroku-buildpack-ruby/CHANGELOG.md +378 -0
  16. data/test/fixtures/buildpacks/heroku-buildpack-ruby/Gemfile +10 -0
  17. data/test/fixtures/buildpacks/heroku-buildpack-ruby/LICENSE +9 -0
  18. data/test/fixtures/buildpacks/heroku-buildpack-ruby/README.md +192 -0
  19. data/test/fixtures/buildpacks/heroku-buildpack-ruby/Rakefile +358 -0
  20. data/test/fixtures/buildpacks/heroku-buildpack-ruby/bin/compile +13 -0
  21. data/test/fixtures/buildpacks/heroku-buildpack-ruby/bin/detect +12 -0
  22. data/test/fixtures/buildpacks/heroku-buildpack-ruby/bin/release +9 -0
  23. data/test/fixtures/buildpacks/heroku-buildpack-ruby/hatchet.json +25 -0
  24. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack.rb +27 -0
  25. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/base.rb +175 -0
  26. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/bundler_lockfile.rb +19 -0
  27. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/disable_deploys.rb +17 -0
  28. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/no_lockfile.rb +16 -0
  29. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/rack.rb +43 -0
  30. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/rails2.rb +91 -0
  31. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/rails3.rb +86 -0
  32. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/rails4.rb +66 -0
  33. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/ruby.rb +681 -0
  34. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/shell_helpers.rb +62 -0
  35. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/bugs_spec.rb +11 -0
  36. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/no_lockfile_spec.rb +10 -0
  37. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/rails23_spec.rb +11 -0
  38. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/rails3_spec.rb +22 -0
  39. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/rails4_spec.rb +12 -0
  40. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/rubies_spec.rb +38 -0
  41. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/spec_helper.rb +35 -0
  42. data/test/fixtures/buildpacks/heroku-buildpack-ruby/support/s3/hmac +79 -0
  43. data/test/fixtures/buildpacks/heroku-buildpack-ruby/support/s3/s3 +223 -0
  44. data/test/fixtures/buildpacks/heroku-buildpack-ruby/vendor/syck_hack.rb +64 -0
  45. data/test/hatchet/allow_failure_anvil_test.rb +21 -0
  46. data/test/hatchet/allow_failure_git_test.rb +17 -0
  47. data/test/hatchet/anvil_test.rb +7 -7
  48. data/test/hatchet/config_test.rb +4 -2
  49. data/test/hatchet/git_test.rb +2 -2
  50. metadata +89 -8
@@ -0,0 +1,62 @@
1
+ module LanguagePack
2
+ module ShellHelpers
3
+ # display error message and stop the build process
4
+ # @param [String] error message
5
+ def error(message)
6
+ Kernel.puts " !"
7
+ message.split("\n").each do |line|
8
+ Kernel.puts " ! #{line.strip}"
9
+ end
10
+ Kernel.puts " !"
11
+ log "exit", :error => message
12
+ exit 1
13
+ end
14
+
15
+ # run a shell comannd and pipe stderr to stdout
16
+ # @param [String] command to be run
17
+ # @return [String] output of stdout and stderr
18
+ def run(command)
19
+ %x{ #{command} 2>&1 }
20
+ end
21
+
22
+ # run a shell command and pipe stderr to /dev/null
23
+ # @param [String] command to be run
24
+ # @return [String] output of stdout
25
+ def run_stdout(command)
26
+ %x{ #{command} 2>/dev/null }
27
+ end
28
+
29
+ # run a shell command and stream the output
30
+ # @param [String] command to be run
31
+ def pipe(command)
32
+ output = ""
33
+ IO.popen(command) do |io|
34
+ until io.eof?
35
+ buffer = io.gets
36
+ output << buffer
37
+ puts buffer
38
+ end
39
+ end
40
+
41
+ output
42
+ end
43
+
44
+ # display a topic message
45
+ # (denoted by ----->)
46
+ # @param [String] topic message to be displayed
47
+ def topic(message)
48
+ Kernel.puts "-----> #{message}"
49
+ $stdout.flush
50
+ end
51
+
52
+ # display a message in line
53
+ # (indented by 6 spaces)
54
+ # @param [String] message to be displayed
55
+ def puts(message)
56
+ message.split("\n").each do |line|
57
+ super " #{line.strip}"
58
+ end
59
+ $stdout.flush
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe "Bugs" do
4
+ context "MRI 1.8.7" do
5
+ it "should install nokogiri" do
6
+ Hatchet::AnvilApp.new("mri_187_nokogiri", :buildpack => buildpack).deploy do |app, heroku, output|
7
+ expect(app).to be_deployed
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe "No Lockfile" do
4
+ it "should not deploy" do
5
+ Hatchet::AnvilApp.new("no_lockfile", :buildpack => buildpack).deploy do |app, heroku, output|
6
+ expect(app).not_to be_deployed
7
+ expect(output).to include("ERROR: Gemfile.lock required")
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe "Rails 2.3.x" do
4
+ it "should deploy on ruby 1.8.7" do
5
+ Hatchet::AnvilApp.new("rails23_mri_187", :buildpack => buildpack).deploy do |app, heroku|
6
+ add_database(app, heroku)
7
+ expect(app).to be_deployed
8
+ expect(successful_body(app)).to eq("hello")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe "Rails 3.x" do
4
+ it "should deploy on ruby 1.9.3" do
5
+ Hatchet::AnvilApp.new("rails3_mri_193", :buildpack => buildpack).deploy do |app, heroku|
6
+ add_database(app, heroku)
7
+ expect(app).to be_deployed
8
+ expect(successful_body(app)).to eq("hello")
9
+ end
10
+ end
11
+
12
+ context "when not using the rails gem" do
13
+ it "should deploy on ruby 1.9.3" do
14
+ Hatchet::AnvilApp.new("railties3_mri_193", :buildpack => buildpack).deploy do |app, heroku, output|
15
+ add_database(app, heroku)
16
+ expect(app).to be_deployed
17
+ expect(output).to match("Ruby/Rails")
18
+ expect(successful_body(app)).to eq("hello")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe "Rails 4.x" do
4
+ it "should deploy on ruby 1.9.3" do
5
+ Hatchet::AnvilApp.new("rails4-manifest", :buildpack => buildpack).deploy do |app, heroku, output|
6
+ add_database(app, heroku)
7
+ expect(app).to be_deployed
8
+ expect(output).to include("Detected manifest file, assuming assets were compiled locally")
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,38 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe "Ruby Versions" do
4
+ it "should deploy ruby 1.8.7 properly" do
5
+ Hatchet::AnvilApp.new("mri_187", :buildpack => buildpack).deploy do |app, heroku, output|
6
+ expect(app).to be_deployed
7
+ expect(successful_body(app)).to match("ruby 1.8.7")
8
+ end
9
+ end
10
+
11
+ it "should deploy ruby 1.9.2 properly" do
12
+ Hatchet::AnvilApp.new("mri_192", :buildpack => buildpack).deploy do |app, heroku, output|
13
+ expect(app).to be_deployed
14
+ expect(successful_body(app)).to match("ruby 1.9.2")
15
+ end
16
+ end
17
+
18
+ it "should deploy ruby 1.9.2 properly (git)" do
19
+ Hatchet::GitApp.new("mri_192", :buildpack => git_repo).deploy do |app, heroku, output|
20
+ expect(app).to be_deployed
21
+ expect(successful_body(app)).to match("ruby 1.9.2")
22
+ end
23
+ end
24
+
25
+ it "should deploy ruby 1.9.3 properly" do
26
+ Hatchet::AnvilApp.new("mri_193", :buildpack => buildpack).deploy do |app, heroku, output|
27
+ expect(app).to be_deployed
28
+ expect(successful_body(app)).to match("ruby 1.9.3")
29
+ end
30
+ end
31
+
32
+ it "should deploy ruby 2.0.0 properly" do
33
+ Hatchet::AnvilApp.new("mri_200", :buildpack => buildpack).deploy do |app, heroku|
34
+ expect(app).to be_deployed
35
+ expect(successful_body(app)).to match("ruby 2.0.0")
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ require 'rspec/core'
2
+ require 'hatchet'
3
+ require 'fileutils'
4
+ require 'hatchet'
5
+
6
+ ENV['RACK_ENV'] = 'test'
7
+
8
+ RSpec.configure do |config|
9
+ config.filter_run :focused => true
10
+ config.run_all_when_everything_filtered = true
11
+ config.alias_example_to :fit, :focused => true
12
+
13
+ config.expect_with :rspec do |c|
14
+ c.syntax = :expect
15
+ end
16
+ config.mock_with :none
17
+ end
18
+
19
+ def buildpack
20
+ File.expand_path(File.dirname(__FILE__) + "/..")
21
+ end
22
+
23
+ def git_repo
24
+ "https://github.com/heroku/heroku-buildpack-ruby.git"
25
+ end
26
+
27
+ def add_database(app, heroku)
28
+ heroku.post_addon(app.name, 'heroku-postgresql:dev')
29
+ _, value = heroku.get_config_vars(app.name).body.detect {|key, value| key.match(/HEROKU_POSTGRESQL_[A-Z]+_URL/) }
30
+ heroku.put_config_vars(app.name, 'DATABASE_URL' => value)
31
+ end
32
+
33
+ def successful_body(app)
34
+ Excon.get("http://#{app.name}.herokuapp.com", :idempotent => true, :expects => 200, :retry_limit => 10).body
35
+ end
@@ -0,0 +1,79 @@
1
+ #!/bin/bash
2
+ # Implement HMAC functionality on top of the OpenSSL digest functions.
3
+ # licensed under the terms of the GNU GPL v2
4
+ # Copyright 2007 Victor Lowther <victor.lowther@gmail.com>
5
+
6
+ die() {
7
+ echo $*
8
+ exit 1
9
+ }
10
+
11
+ check_deps() {
12
+ local res=0
13
+ while [ $# -ne 0 ]; do
14
+ which "${1}" >& /dev/null || { res=1; echo "${1} not found."; }
15
+ shift
16
+ done
17
+ (( res == 0 )) || die "aborting."
18
+ }
19
+
20
+ # write a byte (passed as hex) to stdout
21
+ write_byte() {
22
+ # $1 = byte to write
23
+ printf "\\x$(printf "%x" ${1})"
24
+ }
25
+
26
+ # make an hmac pad out of a key.
27
+ # this is not the most secure way of doing it, but it is
28
+ # the most expedient.
29
+ make_hmac_pad() {
30
+ # using key in file $1 and byte in $2, create the appropriate hmac pad
31
+ # Pad keys out to $3 bytes
32
+ # if key is longer than $3, use hash $4 to hash the key first.
33
+ local x y a size remainder oifs
34
+ (( remainder = ${3} ))
35
+ # in case someone else was messing with IFS.
36
+ for x in $(echo -n "${1}" | od -v -t u1 | cut -b 9-);
37
+ do
38
+ write_byte $((${x} ^ ${2}))
39
+ (( remainder -= 1 ))
40
+ done
41
+ for ((y=0; remainder - y ;y++)); do
42
+ write_byte $((0 ^ ${2}))
43
+ done
44
+ }
45
+
46
+ # utility functions for making hmac pads
47
+ hmac_ipad() {
48
+ make_hmac_pad "${1}" 0x36 ${2} "${3}"
49
+ }
50
+
51
+ hmac_opad() {
52
+ make_hmac_pad "${1}" 0x5c ${2} "${3}"
53
+ }
54
+
55
+ # hmac something
56
+ do_hmac() {
57
+ # $1 = algo to use. Must be one that openssl knows about
58
+ # $2 = keyfile to use
59
+ # $3 = file to hash. uses stdin if none is given.
60
+ # accepts input on stdin, leaves it on stdout.
61
+ # Output is binary, if you want something else pipe it accordingly.
62
+ local blocklen keysize x
63
+ case "${1}" in
64
+ sha) blocklen=64 ;;
65
+ sha1) blocklen=64 ;;
66
+ md5) blocklen=64 ;;
67
+ md4) blocklen=64 ;;
68
+ sha256) blocklen=64 ;;
69
+ sha512) blocklen=128 ;;
70
+ *) die "Unknown hash ${1} passed to hmac!" ;;
71
+ esac
72
+ cat <(hmac_ipad ${2} ${blocklen} "${1}") "${3:--}" | openssl dgst "-${1}" -binary | \
73
+ cat <(hmac_opad ${2} ${blocklen} "${1}") - | openssl dgst "-${1}" -binary
74
+ }
75
+
76
+ [[ ${1} ]] || die "Must pass the name of the hash function to use to ${0}".
77
+
78
+ check_deps od openssl
79
+ do_hmac "${@}"
@@ -0,0 +1,223 @@
1
+ #!/bin/bash
2
+ # basic amazon s3 operations
3
+ # Licensed under the terms of the GNU GPL v2
4
+ # Copyright 2007 Victor Lowther <victor.lowther@gmail.com>
5
+
6
+ set -e
7
+
8
+ basedir="$( cd -P "$( dirname "$0" )" && pwd )"
9
+ PATH="$basedir:$PATH"
10
+
11
+ # print a message and bail
12
+ die() {
13
+ echo $*
14
+ exit 1
15
+ }
16
+
17
+ # check to see if the variable name passed exists and holds a value.
18
+ # Die if it does not.
19
+ check_or_die() {
20
+ [[ ${!1} ]] || die "Environment variable ${1} is not set."
21
+ }
22
+
23
+ # check to see if we have all the needed S3 variables defined.
24
+ # Bail if we do not.
25
+ check_s3() {
26
+ local sak x
27
+ for x in S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY; do
28
+ check_or_die ${x};
29
+ done
30
+ sak="$(echo -n $S3_SECRET_ACCESS_KEY | wc -c)"
31
+ (( ${sak%%[!0-9 ]*} == 40 )) || \
32
+ die "S3 Secret Access Key is not exactly 40 bytes long. Please fix it."
33
+ }
34
+ # check to see if our external dependencies exist
35
+ check_dep() {
36
+ local res=0
37
+ while [[ $# -ne 0 ]]; do
38
+ which "${1}" >& /dev/null || { res=1; echo "${1} not found."; }
39
+ shift
40
+ done
41
+ (( res == 0 )) || die "aborting."
42
+ }
43
+
44
+ check_deps() {
45
+ check_dep openssl date hmac cat grep curl
46
+ check_s3
47
+ }
48
+
49
+ urlenc() {
50
+ # $1 = string to url encode
51
+ # output is on stdout
52
+ # we don't urlencode everything, just enough stuff.
53
+ echo -n "${1}" |
54
+ sed 's/%/%25/g
55
+ s/ /%20/g
56
+ s/#/%23/g
57
+ s/\$/%24/g
58
+ s/\&/%26/g
59
+ s/+/%2b/g
60
+ s/,/%2c/g
61
+ s/:/%3a/g
62
+ s/;/%3b/g
63
+ s/?/%3f/g
64
+ s/@/%40/g
65
+ s/ /%09/g'
66
+ }
67
+
68
+ xmldec() {
69
+ # no parameters.
70
+ # accept input on stdin, put it on stdout.
71
+ # patches accepted to get more stuff
72
+ sed 's/\&quot;/\"/g
73
+ s/\&amp;/\&/g
74
+ s/\&lt;/</g
75
+ s/\&gt;/>/g'
76
+ }
77
+
78
+ ## basic S3 functionality. x-amz-header functionality is not implemented.
79
+ # make an S3 signature string, which will be output on stdout.
80
+ s3_signature_string() {
81
+ # $1 = HTTP verb
82
+ # $2 = date string, must be in UTC
83
+ # $3 = bucket name, if any
84
+ # $4 = resource path, if any
85
+ # $5 = content md5, if any
86
+ # $6 = content MIME type, if any
87
+ # $7 = canonicalized headers, if any
88
+ # signature string will be output on stdout
89
+ local verr="Must pass a verb to s3_signature_string!"
90
+ local verb="${1:?verr}"
91
+ local bucket="${3}"
92
+ local resource="${4}"
93
+ local derr="Must pass a date to s3_signature_string!"
94
+ local date="${2:?derr}"
95
+ local mime="${6}"
96
+ local md5="${5}"
97
+ local headers="${7}"
98
+ printf "%s\n%s\n%s\n%s\n%s\n%s%s" \
99
+ "${verb}" "${md5}" "${mime}" "${date}" \
100
+ "${headers}" "${bucket}" "${resource}" | \
101
+ hmac sha1 "${S3_SECRET_ACCESS_KEY}" | openssl base64 -e -a
102
+ }
103
+
104
+ # cheesy, but it is the best way to have multiple headers.
105
+ curl_headers() {
106
+ # each arg passed will be output on its own line
107
+ local parms=$#
108
+ for ((;$#;)); do
109
+ echo "header = \"${1}\""
110
+ shift
111
+ done
112
+ }
113
+
114
+ s3_curl() {
115
+ # invoke curl to do all the heavy HTTP lifting
116
+ # $1 = method (one of GET, PUT, or DELETE. HEAD is not handled yet.)
117
+ # $2 = remote bucket.
118
+ # $3 = remote name
119
+ # $4 = local name.
120
+ local bucket remote date sig md5 arg inout headers
121
+ # header handling is kinda fugly, but it works.
122
+ bucket="${2:+/${2}}/" # slashify the bucket
123
+ remote="$(urlenc "${3}")" # if you don't, strange things may happen.
124
+ stdopts="--connect-timeout 10 --fail --silent"
125
+ [[ $CURL_S3_DEBUG == true ]] && stdopts="${stdopts} --show-error --fail"
126
+ case "${1}" in
127
+ GET) arg="-o" inout="${4:--}" # stdout if no $4
128
+ ;;
129
+ PUT) [[ ${2} ]] || die "PUT can has bucket?"
130
+ if [[ ! ${3} ]]; then
131
+ arg="-X PUT"
132
+ headers[${#headers[@]}]="Content-Length: 0"
133
+ elif [[ -f ${4} ]]; then
134
+ md5="$(openssl dgst -md5 -binary "${4}"|openssl base64 -e -a)"
135
+ arg="-T" inout="${4}"
136
+ headers[${#headers[@]}]="x-amz-acl: public-read"
137
+ headers[${#headers[@]}]="Expect: 100-continue"
138
+ else
139
+ die "Cannot write non-existing file ${4}"
140
+ fi
141
+ ;;
142
+ DELETE) arg="-X DELETE"
143
+ ;;
144
+ HEAD) arg="-I" ;;
145
+ *) die "Unknown verb ${1}. It probably would not have worked anyways." ;;
146
+ esac
147
+ date="$(TZ=UTC date '+%a, %e %b %Y %H:%M:%S %z')"
148
+ sig=$(s3_signature_string ${1} "${date}" "${bucket}" "${remote}" "${md5}" "" "x-amz-acl:public-read")
149
+
150
+ headers[${#headers[@]}]="Authorization: AWS ${S3_ACCESS_KEY_ID}:${sig}"
151
+ headers[${#headers[@]}]="Date: ${date}"
152
+ [[ ${md5} ]] && headers[${#headers[@]}]="Content-MD5: ${md5}"
153
+ curl ${arg} "${inout}" ${stdopts} -o - -K <(curl_headers "${headers[@]}") \
154
+ "http://s3.amazonaws.com${bucket}${remote}"
155
+ return $?
156
+ }
157
+
158
+ s3_put() {
159
+ # $1 = remote bucket to put it into
160
+ # $2 = remote name to put
161
+ # $3 = file to put. This must be present if $2 is.
162
+ s3_curl PUT "${1}" "${2}" "${3:-${2}}"
163
+ return $?
164
+ }
165
+
166
+ s3_get() {
167
+ # $1 = bucket to get file from
168
+ # $2 = remote file to get
169
+ # $3 = local file to get into. Will be overwritten if it exists.
170
+ # If this contains a path, that path must exist before calling this.
171
+ s3_curl GET "${1}" "${2}" "${3:-${2}}"
172
+ return $?
173
+ }
174
+
175
+ s3_test() {
176
+ # same args as s3_get, but uses the HEAD verb instead of the GET verb.
177
+ s3_curl HEAD "${1}" "${2}" >/dev/null
178
+ return $?
179
+ }
180
+
181
+ # Hideously ugly, but it works well enough.
182
+ s3_buckets() {
183
+ s3_get |grep -o '<Name>[^>]*</Name>' |sed 's/<[^>]*>//g' |xmldec
184
+ return $?
185
+ }
186
+
187
+ # this will only return the first thousand entries, alas
188
+ # Mabye some kind soul can fix this without writing an XML parser in bash?
189
+ # Also need to add xml entity handling.
190
+ s3_list() {
191
+ # $1 = bucket to list
192
+ [ "x${1}" == "x" ] && return 1
193
+ s3_get "${1}" |grep -o '<Key>[^>]*</Key>' |sed 's/<[^>]*>//g'| xmldec
194
+ return $?
195
+ }
196
+
197
+ s3_delete() {
198
+ # $1 = bucket to delete from
199
+ # $2 = item to delete
200
+ s3_curl DELETE "${1}" "${2}"
201
+ return $?
202
+ }
203
+
204
+ # because this uses s3_list, it suffers from the same flaws.
205
+ s3_rmrf() {
206
+ # $1 = bucket to delete everything from
207
+ s3_list "${1}" | while read f; do
208
+ s3_delete "${1}" "${f}";
209
+ done
210
+ }
211
+
212
+ check_deps
213
+ case $1 in
214
+ put) shift; s3_put "$@" ;;
215
+ get) shift; s3_get "$@" ;;
216
+ rm) shift; s3_delete "$@" ;;
217
+ ls) shift; s3_list "$@" ;;
218
+ test) shift; s3_test "$@" ;;
219
+ buckets) s3_buckets ;;
220
+ rmrf) shift; s3_rmrf "$@" ;;
221
+ *) die "Unknown command ${1}."
222
+ ;;
223
+ esac