heroku_hatchet 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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