Skip to content

Commit 173feef

Browse files
authored
Better errors on bootstrap failure (heroku#1629)
* Show original error on bootstrap failed download Current ``` curl --fail --retry 3 --retry-connrefused --connect-timeout "3" --silent --location -o "/tmp/ruby.tgz" "https://heroku-buildpack-ruby.s3.us-east-1.amazonaws.com/heroku-20/ruby-3.3.9.tgz"; echo "Done $?" # => Done 22 ``` After ``` curl --fail --show-error --retry 3 --retry-connrefused --connect-timeout "3" --silent --location -o "/tmp/ruby.tgz" "https://heroku-buildpack-ruby.s3.us-east-1.amazonaws.com/heroku-20/ruby-3.3.9.tgz"; echo "Done $?" # => curl: (22) The requested URL returned error: 404 # => Done 22 ``` Ref heroku#1628 * Don't silently ignore bash failures This output reported in heroku#1628 is confusing. ``` remote: /tmp/buildpackLSI7t/bin/compile: line 35: Failed to download a Ruby executable for bootstrapping! remote: remote: This is most likely a temporary internal error. If the problem remote: persists, make sure that you are not running a custom or forked remote: version of the Heroku Ruby buildpack which may need updating. remote: remote: url: https://heroku-buildpack-ruby.s3.us-east-1.amazonaws.com/heroku-20/ruby-3.3.9.tgz remote: /tmp/tmp.PDOWE1VL34/bin/ruby: File name too long ``` Even though that function exits non-zero, it doesn't propagate due to a missing `set -e` at the top level. This ensures all top level `bin/` executables exit on failure. * Explicit heroku-20 check and failure Issue heroku#1628 shows that it's unclear when a Ruby version cannot be found on an unsupported stack, and that it is expected behavior. To avoid this confusion, we can explicitly error when using a recently EOL stack. * Better stack error message This guards against execution using unknown or unexpected stacks. In addition, its behavior is tested. * Fix shellcheck When I added this function with local variable: ``` function checks::ensure_supported_stack() { local stack="${1}" ``` It triggered this shellcheck failure on an unrelated line: ``` $ shellcheck bin/support/bash_functions.sh -x In bin/support/bash_functions.sh line 35: if [ "$STACK" == "heroku-24" ]; then ^----^ SC2153 (info): Possible misspelling: STACK may not be assigned. Did you mean stack? ``` This fixes the lint.
1 parent 215828f commit 173feef

7 files changed

Lines changed: 76 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [Unreleased]
44

5+
- Explicitly error on end-of-life (EOL) stack `heroku-20` (https://github.com/heroku/heroku-buildpack-ruby/pull/1629)
56
- Default Ruby version is now 3.3.9 (https://github.com/heroku/heroku-buildpack-ruby/pull/1624)
67

78
## [v315] - 2025-07-24

bin/compile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# The actual compilation code lives in `bin/support/ruby_compile`. This file instead
33
# bootstraps the ruby needed and then executes `bin/support/ruby_compile`
44

5+
set -euo pipefail
6+
57
BUILD_DIR=$1
68
CACHE_DIR=$2
79
ENV_DIR=$3
@@ -11,6 +13,8 @@ BUILDPACK_DIR=$(dirname "$BIN_DIR")
1113
# shellcheck source=bin/support/bash_functions.sh
1214
source "$BIN_DIR/support/bash_functions.sh"
1315

16+
checks::ensure_supported_stack "${STACK:?Required env var STACK is not set}"
17+
1418
bootstrap_ruby_dir=$(install_bootstrap_ruby "$BIN_DIR" "$BUILDPACK_DIR")
1519
export PATH="$bootstrap_ruby_dir/bin/:$PATH"
1620
unset GEM_PATH

bin/detect

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
#!/bin/sh
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
24

35
APP_DIR=$1
46

bin/support/bash_functions.sh

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ install_bootstrap_ruby()
3030
{
3131
local bin_dir=$1
3232
local buildpack_dir=$2
33+
local stack="${STACK:?Required env var STACK is not set}"
3334

3435
# Multi-arch aware stack support
35-
if [ "$STACK" == "heroku-24" ]; then
36+
if [ "$stack" == "heroku-24" ]; then
3637
local arch
3738
arch=$(dpkg --print-architecture)
3839
local heroku_buildpack_ruby_dir="$buildpack_dir/vendor/ruby/$STACK/$arch"
@@ -154,3 +155,43 @@ compile_buildpack_v2()
154155
fi
155156
}
156157

158+
# After a stack is EOL, updates to the buildpack may fail with unexpected or unhelpful errors.
159+
# This can happen when the buildpack is being used off of the platform such as with `dokku`
160+
# which is not officially supported.
161+
function checks::ensure_supported_stack() {
162+
local stack="${1}"
163+
164+
case "${stack}" in
165+
# When removing support from a stack, move it to the bottom of the list
166+
heroku-22 | heroku-24)
167+
return 0
168+
;;
169+
heroku-18 | heroku-20)
170+
# This error will only ever be seen on non-Heroku environments, since the
171+
# Heroku build system rejects builds using EOL stacks.
172+
cat <<-EOF
173+
Error: The '${stack}' stack is no longer supported.
174+
175+
This buildpack no longer supports the '${stack}' stack since it has
176+
reached its end-of-life:
177+
https://devcenter.heroku.com/articles/stack#stack-support-details
178+
179+
Upgrade to a newer stack to continue using this buildpack.
180+
EOF
181+
exit 1
182+
;;
183+
*)
184+
cat <<-EOF
185+
Error: The '${stack}' stack isn't recognised.
186+
187+
This buildpack doesn't recognise or support the '${stack}' stack.
188+
189+
If '${stack}' is a valid stack, make sure that you are using the latest
190+
version of this buildpack and haven't pinned to an older release:
191+
https://devcenter.heroku.com/articles/managing-buildpacks#view-your-buildpacks
192+
https://devcenter.heroku.com/articles/managing-buildpacks#classic-buildpacks-references
193+
EOF
194+
exit 1
195+
;;
196+
esac
197+
}

bin/support/download_ruby

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ fi
5151

5252
mkdir -p "$RUBY_BOOTSTRAP_DIR"
5353

54-
curl_retry_on_18 --fail --retry 3 --retry-connrefused --connect-timeout "${CURL_CONNECT_TIMEOUT:-3}" --silent --location -o "$RUBY_BOOTSTRAP_DIR/ruby.tgz" "$heroku_buildpack_ruby_url" || {
54+
curl_retry_on_18 --fail --show-error --retry 3 --retry-connrefused --connect-timeout "${CURL_CONNECT_TIMEOUT:-3}" --silent --location -o "$RUBY_BOOTSTRAP_DIR/ruby.tgz" "$heroku_buildpack_ruby_url" || {
5555
cat<<EOF
5656
Failed to download a Ruby executable for bootstrapping!
5757

bin/test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# The actual `bin/test-compile` code lives in `bin/ruby_test-compile`. This file instead
33
# bootstraps the ruby needed and then executes `bin/ruby_test-compile`
44

5+
set -euo pipefail
6+
57
BIN_DIR=$(cd "$(dirname "$0")" || exit; pwd) # absolute path
68
BUILDPACK_DIR=$(dirname "$BIN_DIR")
79

spec/unit/bash_functions_spec.rb

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
require 'spec_helper'
22

33
describe "Bash functions" do
4+
it "fails on old stacks" do
5+
out = exec_with_bash_functions(<<~EOM, raise_on_fail: false)
6+
checks::ensure_supported_stack "heroku-20"
7+
EOM
8+
9+
expect($?.success?).to be_falsey, "Expected command failure but got unexpected success. Output:\n\n#{out}"
10+
expect(out).to include("This buildpack no longer supports the 'heroku-20' stack")
11+
end
12+
13+
it "knows the latest stacks" do
14+
out = exec_with_bash_functions(<<~EOM)
15+
checks::ensure_supported_stack "heroku-24"
16+
EOM
17+
18+
expect(out).to be_empty
19+
end
20+
421
it "Detects jruby in the Gemfile.lock" do
522
Dir.mktmpdir do |dir|
623
dir = Pathname(dir)
@@ -9,7 +26,6 @@
926
ruby 2.5.7p001 (jruby 9.2.13.0)
1027
EOM
1128

12-
1329
out = exec_with_bash_functions <<~EOM
1430
which_java()
1531
{
@@ -75,7 +91,7 @@ def bash_functions_file
7591
root_dir.join("bin", "support", "bash_functions.sh")
7692
end
7793

78-
def exec_with_bash_functions(code, stack: "heroku-24")
94+
def exec_with_bash_functions(code, stack: "heroku-24", raise_on_fail: true)
7995
contents = <<~EOM
8096
#! /usr/bin/env bash
8197
set -eu
@@ -103,21 +119,23 @@ def exec_with_bash_functions(code, stack: "heroku-24")
103119
out = "Command timed out"
104120
success = false
105121
end
106-
unless success
122+
123+
if raise_on_fail && !success
107124
message = <<~EOM
108125
Contents:
109126
110127
#{contents.lines.map.with_index { |line, number| " #{number.next} #{line.chomp}"}.join("\n") }
111128
112-
Expected running script to succeed, but it did not
129+
Expected running script to succeed, but it did not. If this was expected, use `raise_on_fail: false`
113130
114131
Output:
115132
116133
#{out}
117134
EOM
118135

119136
raise message
137+
else
138+
out
120139
end
121-
out
122140
end
123141
end

0 commit comments

Comments
 (0)