From ca763a87e0e02f63bb4dc6c58ae71f5cadcba3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20KUBLER?= Date: Thu, 3 Sep 2026 11:34:18 +0200 Subject: [PATCH 1/9] feat: introduce support for flavor There are now two "flavors" of webapp-runner: - a "default" one, which is the one we support since ever ; - a "main" one, which doesn't include Memcached and Redis client libraries. The "main" flavor is sometimes useful, because some libraries can sometimes conflict with similar ones used in the app. This commit also introduces the use of two INVENTORY-*.tsv files (one for each flavor). --- INVENTORY-default.tsv | 13 +++ INVENTORY-main.tsv | 13 +++ bin/compile | 207 ++++++++++++++++++++++++++---------------- 3 files changed, 157 insertions(+), 76 deletions(-) create mode 100644 INVENTORY-default.tsv create mode 100644 INVENTORY-main.tsv diff --git a/INVENTORY-default.tsv b/INVENTORY-default.tsv new file mode 100644 index 0000000..2e650c1 --- /dev/null +++ b/INVENTORY-default.tsv @@ -0,0 +1,13 @@ +# This is a tab-separated file. +# Each row has its own line and must have 3 columns (fields) separated by a tab character: version, URL and checksum. +# The default value (the one used as default by the buildpack) must have a fourth column, whose value MUST be "default". +# Only one row can have the "default" column. +# Lines starting with a # are treated as comments. Empty lines are ignored. + +10.1.59.0 https://buildpacks-repository.s3.eu-central-1.amazonaws.com/webapp-runner-10.1.59.0.jar sha256:4039bb1d41680a5c2aac4bca4ae93e58f8a764def81f434923563401610739da +10.1.57.0 https://buildpacks-repository.s3.eu-central-1.amazonaws.com/webapp-runner-10.1.57.0.jar sha256:ac1013f20aca7489dba9f690b6a3d5db8e123821e9f26041714719d41ec0509d +10.1.56.0 https://buildpacks-repository.s3.eu-central-1.amazonaws.com/webapp-runner-10.1.56.0.jar sha256:482dc7c585058b0066dc6a435400467aa8f634a7beaac807d8743fc946ab088b + +9.0.121.0 https://buildpacks-repository.s3.eu-central-1.amazonaws.com/webapp-runner-9.0.121.0.jar sha256:a8d6f2dc2bcfa9467d6f8509fc89e7d63b443c0774f139258b1897dcd114d814 default +9.0.120.0 https://buildpacks-repository.s3.eu-central-1.amazonaws.com/webapp-runner-9.0.120.0.jar sha256:9d4830d1d2a5a60a509d7e7486541e7211b051df8f03a2bb0ef995f9f0acff80 +9.0.119.0 https://buildpacks-repository.s3.eu-central-1.amazonaws.com/webapp-runner-9.0.119.0.jar sha256:cbe86423a51bcee590bfc176c6f693f403c747ad464577d928b8ff338b9c088d diff --git a/INVENTORY-main.tsv b/INVENTORY-main.tsv new file mode 100644 index 0000000..ecfb867 --- /dev/null +++ b/INVENTORY-main.tsv @@ -0,0 +1,13 @@ +# This is a tab-separated file. +# Each row has its own line and must have 3 columns (fields) separated by a tab character: version, URL and checksum. +# The default value (the one used as default by the buildpack) must have a fourth column, whose value MUST be "default". +# Only one row can have the "default" column. +# Lines starting with a # are treated as comments. Empty lines are ignored. + +10.1.59.0 https://buildpacks-repository.s3.eu-central-1.amazonaws.com/webapp-runner-main-10.1.59.0.jar sha256:e4c5a43a4032f3bf4ce78e4f00d5bcb5f382d033538405003350d5d8cc9bea58 +10.1.57.0 https://buildpacks-repository.s3.eu-central-1.amazonaws.com/webapp-runner-main-10.1.57.0.jar sha256:e110531d879a03c4cd2335442e9700e59900631a11079c6d12329413e9d20ed5 +10.1.56.0 https://buildpacks-repository.s3.eu-central-1.amazonaws.com/webapp-runner-main-10.1.56.0.jar sha256:4a20bc4c7131aff0beae8c34b0765dd07cc7f399e13a1f986e5b678dc8c3ca0c + +9.0.121.0 https://buildpacks-repository.s3.eu-central-1.amazonaws.com/webapp-runner-main-9.0.121.0.jar sha256:e215d430ed800e9675cf70c7ce61c8e307b8ff611d66ef44199658643f1ea38b default +9.0.120.0 https://buildpacks-repository.s3.eu-central-1.amazonaws.com/webapp-runner-main-9.0.120.0.jar sha256:5829b7c99a2094247dcb63d5972f8246e8a6ef80afff294320a9ebd951ae724a +9.0.119.0 https://buildpacks-repository.s3.eu-central-1.amazonaws.com/webapp-runner-main-9.0.119.0.jar sha256:ec6e64b0ab590711e1e0861360e789b15a64a5c457c6491eabf0dad0b6a9d41a diff --git a/bin/compile b/bin/compile index 0663604..6fe3b77 100755 --- a/bin/compile +++ b/bin/compile @@ -1,108 +1,163 @@ #!/usr/bin/env bash -# bin/compile +# usage: bin/compile -set -eo pipefail +cmnlib="$( readlink -f "$( dirname "${0}" )/../vendor/cmnlib.sh" )" +# shellcheck source=vendor/cmnlib.sh +source "${cmnlib}" -if [[ -n "${BUILDPACK_DEBUG}" ]]; then - set -x -fi -build_dir="${1}" -cache_dir="${2}" -env_dir="${3}" +cmn::main::start "${0}" "${1}" "${2}" "${3}" + +# ----------------------------------------------------------------------------- + +# Determine which flavor of webapp-runner to install: +flavor="${JAVA_WEBAPP_RUNNER_FLAVOR:-"default"}" + +# Inventory file: +inventory="${buildpack_dir:?}/INVENTORY-${flavor}.tsv" + +# This statement won't fail even if cmn::inventory::get_default were to fail. +version="${JAVA_WEBAPP_RUNNER_VERSION:-${WEBAPP_RUNNER_VERSION:-$( cmn::inventory::get_default "${inventory}" )}}" + +if [[ -z "${version}" ]]; then + cmn::main::fail 1 <<- EOM + Unable to determine which version of webapp runner to deploy. + It seems like there's no default version in the inventory file either, + which isn't OK. + + Please: + 1. Set JAVA_WEBAPP_RUNNER_VERSION environment variable to the desired + value, and try to redeploy. It should pass. + 2. Either reach out to our Support Team to let them know about this + situation, or file an issue in the java-war-buildpack repository + (https://github.com/Scalingo/java-war-buildpack/issues). + Thanks <3 -readonly java_version="${JAVA_VERSION:-1.8}" -readonly webapp_runner_version="${JAVA_WEBAPP_RUNNER_VERSION:-${WEBAPP_RUNNER_VERSION:-9.0.120.0}}" + Aborting for now. + EOM +fi -readonly base_dir="$( cd -P "$( dirname "$0" )" && pwd )" -readonly buildpack_dir="$( readlink -f "${base_dir}/.." )" +if ! archive_url="$( cmn::inventory::get_url "${inventory}" "${version}" )" +then + cmn::main::fail 1 <<- EOM + Unable to retrieve download URL for version ${version}". + It seems like there's an issue with the buildpack inventory file. + Please either reach out to our Support Team to let them know about this + situation, or file an issue in the java-war-buildpack repository + (https://github.com/Scalingo/java-war-buildpack/issues) + Thanks <3 -source "${buildpack_dir}/lib/common.sh" + Aborting for now. + EOM +fi -export_env_dir "${env_dir}" +if ! archive_checksum="$( cmn::inventory::get_checksum "${inventory}" "${version}" )" +then + cmn::main::fail 1 <<- EOM + Unable to retrieve checksum for version ${version}". + It seems like there's an issue with the buildpack inventory file. + Please either reach out to our Support Team to let them know about this + situation, or file an issue in the java-war-buildpack repository + (https://github.com/Scalingo/java-war-buildpack/issues) + Thanks <3 -# Installs Java and webapp_runner -# -# Usage: install_webapp_runner -# -install_webapp_runner() { - local jvm_url - local runner_url + Aborting for now. + EOM +fi - local build_d - local cache_d - local buildpack_d +cache_file="${cache_dir:?}/webapp-runner-${flavor}-${version}.jar" + +# Determine which version of JDK to install. +# By order of precedence: +# 1. $BUILD_DIR/system.properties (handled by user) +# 2. $JAVA_VERSION (handled by user) +# 3. Default version for $STACK (handled by buildpack-jvm-common) +if [[ ! -f "${build_dir:?}/system.properties" ]]; then + if [[ -n "${JAVA_VERSION}" ]]; then + echo "java.runtime.version=${JAVA_VERSION}" \ + > "${build_dir:?}/system.properties" + fi +fi - local tmp_d - local jre_version - local runner_version +cmn::output::info "Deploying Webapp Runner (${flavor}) ${version}" - local cached_jvm_common - local cached_runner +# ----------------------------------------------------------------------------- - build_d="${1}" - cache_d="${2}" - buildpack_d="${3}" - jre_version="${4}" - runner_version="${5}" +cmn::step::start "Installing JVM" - local buildpacks_repository_url="https://buildpacks-repository.s3.eu-central-1.amazonaws.com" +# Use buildpack-jvm-common buildpack to setup JVM: +cmn::bp::run "${build_dir:?}" "${cache_dir:?}" "${env_dir:?}" "${tmp_dir:?}" \ + "https://github.com/Scalingo/buildpack-jvm-common" - jvm_url="${JVM_COMMON_BUILDPACK:-"${buildpacks_repository_url}/jvm-common.tar.xz"}" - runner_url="${buildpacks_repository_url}/webapp-runner-${runner_version}.jar" +# ----------------------------------------------------------------------------- - echo "-----> Installing Webapp Runner ${runner_version}..." +cmn::step::start "Installing Webapp Runner" - # Install JVM common tools: +if [[ ! -f "${cache_file}" ]]; then + # The archive doesn't exist in the cache. - cached_jvm_common="${cache_d}/jvm-common.tar.xz" + # Remove any existing cached archive, as they are probably for another + # version: + cmn::task::start "Cleaning cache" + rm --recursive --force "${cache_dir:?}"/webapp-runner*.jar + cmn::task::finish - if [ ! -f "${cached_jvm_common}" ] - then - curl --location --silent --retry 6 --retry-connrefused --retry-delay 0 \ - "${jvm_url}" \ - --output "${cached_jvm_common}" - fi + # Download appropriate version: + cmn::task::start "Downloading" + if ! cmn::file::download "${archive_url}" "${cache_file}"; then + cmn::main::fail "${?}" <<- EOM + Unable to download Webapp Runner. + Aborting. + EOM + fi + cmn::task::finish +else + # The archive is in cache. - tmp_d=$( mktemp -d jvm-common-XXXXXX ) && { - tar --extract --xz --touch --strip-components=1 \ - --file "${cached_jvm_common}" \ - --directory "${tmp_d}" + cmn::output::info "Found matching file in cache." +fi + +# From this point, we have an archive in cache. - # Source utilities and functions: - source "${tmp_d}/bin/java" +# We always check the archive validity to prevent cache corruption and other +# issues. +cmn::task::start "Checking archive validity" +if ! cmn::file::validate_checksum "${cache_file}" "${archive_checksum}"; then + # File in cache doesn not seem valid, + # remove it so a future run can be successful + rm --force "${cache_file}" - echo "java.runtime.version=${jre_version}" \ - > "${build_d}/system.properties" + cmn::main::fail 2 <<-EOM + The checksum for the archive file in the cache doesn't match what's + expected. - install_openjdk "${build_d}" "${buildpack_d}" + Here are the most common causes: - rm -Rf "${tmp_d}" - } + - A networking issue occurred, causing the downloaded file to + be incomplete or corrupt. Triggering a new deployment should force + the platform to download the archive again, and the deployment should + pass. - # Install Webapp Runner - cached_runner="${cache_d}/webapp-runner-${runner_version}.jar" - if [ ! -f "${cached_runner}" ]; then - echo "-----> Downloading webapp runner" + - Upstream replaced the archive and its checksum, resulting in the + same version number having a different checksum. This could mean a + malicious threat tampered the source archive and its checksum. + Please check upstream for a potential explanation and make sure it's + still trustable. - curl --location --silent --retry 6 --retry-connrefused --retry-delay 0 \ - "${runner_url}" \ - --output "${cached_runner}" \ - || { - echo "Unable to download webapp runner ${runner_version}. Aborting." >&2 - exit 1 - } - else - echo "-----> Got webapp runner from the cache" - fi + In all cases, the cached archive has been removed to force a new + download during the next deployment attempt. - cp "${cached_runner}" "${build_d}/webapp-runner.jar" -} + Aborting. + EOM +fi +cmn::task::finish -readonly -f install_webapp_runner +cmn::task::start "Installing" +cp "${cache_file}" "${build_dir:?}/webapp-runner.jar" +cmn::task::finish +# ----------------------------------------------------------------------------- -install_webapp_runner "${build_dir}" "${cache_dir}" "${buildpack_dir}" \ - "${java_version}" "${webapp_runner_version}" +cmn::main::finish From 912bd997e6f19c98d8a43a2955ddaececfc1a00c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20KUBLER?= Date: Thu, 3 Sep 2026 11:38:31 +0200 Subject: [PATCH 2/9] fix(tests): fix tests to reflect the latest updates - Removes tests for JVM 18 and 19 - Adds tests for JVM 21 (LTS) and 25 (LTS) - Updates a few test::utils functions for better output - Updates default versions of webapp-runner --- Makefile | 12 +- test/helpers | 292 +++++++------- test/run | 76 ++-- test/tests | 193 +++++----- test/utils | 1040 +++++++++++++++++++++++++++----------------------- 5 files changed, 857 insertions(+), 756 deletions(-) diff --git a/Makefile b/Makefile index eaafd31..d9be43d 100644 --- a/Makefile +++ b/Makefile @@ -4,12 +4,14 @@ BASH_COMMAND := /bin/bash .DEFAULT := all all: test -test: test22 +test: test22 test24 test26 +test26: STACK := "scalingo-26" +test24: STACK := "scalingo-24" test22: STACK := "scalingo-22" -test22: BASH_COMMAND := test/run -test22: IMAGE := "scalingo/$(STACK):latest" -test22: +test22 test24 test26: BASH_COMMAND := test/run +test22 test24 test26: IMAGE := "scalingo/$(STACK):latest" +test22 test24 test26: @echo "Running tests in Docker using $(IMAGE)" - @docker run --pull always --mount type=bind,src=$(PWD),dst=/buildpack --workdir /buildpack --rm --interactive --tty --env "GITLAB_TOKEN=$(GITLAB_TOKEN)" --env "GITHUB_TOKEN=$(GITHUB_TOKEN)" --env "STACK=$(STACK)" $(IMAGE) bash -c "$(BASH_COMMAND)" + @docker run --pull always --mount type=bind,src=$(PWD),dst=/buildpack,readonly --workdir /buildpack --rm --interactive --tty --env "GITLAB_TOKEN=$(GITLAB_TOKEN)" --env "GITHUB_TOKEN=$(GITHUB_TOKEN)" --env "STACK=$(STACK)" $(IMAGE) bash -c "$(BASH_COMMAND)" diff --git a/test/helpers b/test/helpers index 31f75ea..d1a1c85 100644 --- a/test/helpers +++ b/test/helpers @@ -3,173 +3,191 @@ ### Helpers functions test::helpers::common_deploy() { - # Deploys a .war app step by step and tests that everything related to the - # PHP setup is as expected. - # To do so, this function runs the buildpack scripts and mimicks the - # platform behavior wherever needed. - # - # Globals: - # STD_OUT - # - # Arguments: - # $1 > $detect_expectation - # See `test::helpers::detect`. - # - # $2 > $php_version - # See `test::helpers::php`. - - local detect_expectation="${1}" - local java_version="${2}" - local webapp_runner_version="${3}" - - # Test bin/detect: - test::helpers::detect "${detect_expectation}" - - # Test bin/compile: - test::helpers::compile - - # Switch environment: - test::utils::enter_prod - - # Test Java version: - test::helpers::jvm "${java_version}" - - # Test webapp-runner version: - test::helpers::tomcat "${webapp_runner_version}" - - # Test that the Tomcat server works: - test::helpers::http_code 200 +# +# Deploys a .war app step by step and tests that everything related to the +# PHP setup is as expected. +# To do so, this function runs the buildpack scripts and mimicks the +# platform behavior wherever needed. +# +# Globals: +# STD_OUT +# +# Arguments: +# $1 > $detect_expectation +# See `test::helpers::detect`. +# +# $2 > $php_version +# See `test::helpers::php`. +# + + local detect_expectation="${1}" + local java_version="${2}" + local webapp_runner_version="${3}" + + # Test bin/detect: + test::helpers::detect "${detect_expectation}" + + # Test bin/compile: + test::helpers::compile + + # Switch environment: + test::utils::enter_prod + + # Test Java version: + test::helpers::jvm "${java_version}" + + # Test webapp-runner version: + test::helpers::tomcat "${webapp_runner_version}" + + # Test that the Tomcat server works: + test::helpers::http_code 200 } test::helpers::detect() { - # Runs the `bin/detect` script of the buildpack and then: - # - Asserts that it succeeded - # - Asserts that the output equals what's expected. - # - # Arguments: - # $1 > $expected_output - # The value of this variable must equal the actual output of the - # `bin/detect` script for the assertion to pass. - - local expected_output="${1}" - - test::utils::detect - - test::utils::assertCapturedSuccess - test::utils::assertCapturedEquals "${expected_output}" +# +# Runs the `bin/detect` script of the buildpack and then: +# - Asserts that it succeeded +# - Asserts that the output equals what's expected. +# +# Arguments: +# $1 > $expected_output +# The value of this variable must equal the actual output of the +# `bin/detect` script for the assertion to pass. +# + + local expected_output="${1}" + + test::utils::detect + + test::utils::assertCapturedSuccess + test::utils::assertCapturedEquals "${expected_output}" } test::helpers::compile() { - # Runs the `bin/compile` script of the buildpack and then: - # - Asserts that is succeeded +# +# Runs the `bin/compile` script of the buildpack and then: +# - Asserts that is succeeded +# - test::utils::compile + test::utils::compile - test::utils::assertCapturedSuccess + test::utils::assertCapturedSuccess } test::helpers::jvm() { - # Tests that the JVM has the expected version. - # - # Arguments: - # $1 > $version: Expected JVM version +# +# Tests that the JVM has the expected version. +# +# Arguments: +# $1 > $version: Expected JVM version +# - local version="${1}" + local version="${1}" - test::utils::capture java -version + test::utils::capture java -version - # The previous command outputs to stderr... - # Consequently, we have to: - test::utils::assertErrorMatches "openjdk version \"${version}" + # The previous command outputs to stderr... + # Consequently, we have to: + test::utils::assertErrorMatches "openjdk version \"${version}" } test::helpers::tomcat() { - # Tests that Tomcat (webapp-runner) has the expected version. - # - # Arguments: - # $1 > $version: Expected Tomcat version +# +# Tests that Tomcat (webapp-runner) has the expected version. +# +# Arguments: +# $1 > $version: Expected Tomcat version +# - local version="${1}" + local version="${1}" - test::utils::capture java -classpath webapp-runner.jar \ - org.apache.catalina.util.ServerInfo + test::utils::capture java -classpath webapp-runner.jar \ + org.apache.catalina.util.ServerInfo - test::utils::assertCapturedMatches "Server number: +${version}" + test::utils::assertCapturedMatches "Server number: +${version}" } test::helpers::_set_path() { - # Helper to adjust the PATH environment variable. - # Automatically called by `test::utils::enter_prod`. - # - # Globals: - # PATH - # - # See also: - # - `test::utils::enter_prod` - - # Adjust PATH: - PATH="${PATH}:${HOME}/bin:${HOME}/.jdk/bin" - export PATH +# +# Helper to adjust the PATH environment variable. +# Automatically called by `test::utils::enter_prod`. +# +# Globals: +# PATH +# +# See also: +# - `test::utils::enter_prod` +# + + # Adjust PATH: + PATH="${PATH}:${HOME}/bin:${HOME}/.jdk/bin" + export PATH } test::helpers::http_code() { - # Asserts that the Tomcat server running on localhost:3000 answers with the - # given HTTP code. - # - # Starts the Tomcat server, sends a GET request to localhost:3000, compares - # the HTTP code returned and stops the Tomcat server. - # - # Arguments: - # $1 > $code: Expected HTTP code. - - local code="${1}" - local url="http://localhost:3000" - - test::helpers::start_tomcat >/dev/null 2>&1 - test::utils::capture curl -s -o /dev/null -w "%{http_code}" "${url}" - test::helpers::stop_tomcat - - test::utils::assertCapturedEquals "${code}" +# +# Asserts that the Tomcat server running on localhost:3000 answers with the +# given HTTP code. +# +# Starts the Tomcat server, sends a GET request to localhost:3000, compares +# the HTTP code returned and stops the Tomcat server. +# +# Arguments: +# $1 > $code: Expected HTTP code. +# + + local code="${1}" + local url="http://localhost:3000" + + test::helpers::start_tomcat >/dev/null 2>&1 + test::utils::capture curl -s -o /dev/null -w "%{http_code}" "${url}" + test::helpers::stop_tomcat + + test::utils::assertCapturedEquals "${code}" } test::helpers::start_tomcat() { - # Starts Tomcat, like the platform would (as specified in `bin/release`). - # - # The server will start on localhost:3000 - # We also store the pid of the process in `$BUILD_DIR/.pid`, so we can kill - # it later. - # - # Globals: - # PORT - # JAVA_OPTS - # WAR_PATH - # BUILD_DIR - - PORT=3000 - export PORT - - JAVA_OPTS="-Xmx384m -Xss512k -XX:+UseCompressedOops" - export JAVA_OPTS - - if [ -n "${WAR_PATH}" ]; then - java ${JAVA_OPTS} -jar ./webapp-runner.jar \ - --port "${PORT}" \ - "${WAR_PATH}" & - else - java ${JAVA_OPTS} -jar ./webapp-runner.jar \ - --port "${PORT}" \ - ./*.war & - fi - - echo $! > "${BUILD_DIR}/.pid" - sleep 5 +# +# Starts Tomcat, like the platform would (as specified in `bin/release`). +# +# The server will start on localhost:3000 +# We also store the pid of the process in `$BUILD_DIR/.pid`, so we can kill +# it later. +# +# Globals: +# PORT +# JAVA_OPTS +# WAR_PATH +# BUILD_DIR +# + + PORT=3000 + export PORT + + JAVA_OPTS="-Xmx384m -Xss512k -XX:+UseCompressedOops" + export JAVA_OPTS + + if [ -n "${WAR_PATH}" ]; then + java ${JAVA_OPTS} -jar ./webapp-runner.jar \ + --port "${PORT}" \ + "${WAR_PATH}" & + else + java ${JAVA_OPTS} -jar ./webapp-runner.jar \ + --port "${PORT}" \ + ./*.war & + fi + + echo $! > "${BUILD_DIR}/.pid" + sleep 5 } test::helpers::stop_tomcat() { - # Stops the currently running Tomcat server. - # - # Globals: - # BUILD_DIR - - kill -TERM "$( cat "${BUILD_DIR}/.pid" )" +# +# Stops the currently running Tomcat server. +# +# Globals: +# BUILD_DIR +# + + kill -TERM "$( cat "${BUILD_DIR}/.pid" )" } diff --git a/test/run b/test/run index d7a87b3..3ce5f4b 100755 --- a/test/run +++ b/test/run @@ -3,67 +3,69 @@ buildpack_dir="${BUILDPACK_DIR:-"$( pwd )"}" suite() { - # Load tests from the following file: - source "${buildpack_dir}/test/tests" + # Load tests from the following file: + source "${buildpack_dir}/test/tests" } oneTimeSetUp() { - TEST_SUITE_CACHE="$( mktemp --directory "${SHUNIT_TMPDIR}/test_suite_cache.XXXX" )" + TEST_SUITE_CACHE="$( mktemp --directory "${SHUNIT_TMPDIR}/test_suite_cache.XXXX" )" } oneTimeTearDown() { - rm --recursive --force "${TEST_SUITE_CACHE}" + rm --recursive --force "${TEST_SUITE_CACHE}" } setUp() { - OUTPUT_DIR="$( mktemp --directory "${SHUNIT_TMPDIR}/output.XXXX" )" + OUTPUT_DIR="$( mktemp --directory "${SHUNIT_TMPDIR}/output.XXXX" )" - STD_OUT="${OUTPUT_DIR}/stdout" - STD_ERR="${OUTPUT_DIR}/stderr" + STD_OUT="${OUTPUT_DIR}/stdout" + STD_ERR="${OUTPUT_DIR}/stderr" - BUILD_DIR="${OUTPUT_DIR}/build" - CACHE_DIR="${OUTPUT_DIR}/cache" - HOME="${BUILD_DIR}" - BUILDPACK_DIR="$( mktemp --directory "/tmp/test-XXXXXX" )" + BUILD_DIR="${OUTPUT_DIR}/build" + CACHE_DIR="${OUTPUT_DIR}/cache" + ENV_DIR="${OUTPUT_DIR}/env" + HOME="${BUILD_DIR}" + BUILDPACK_DIR="$( mktemp --directory "/tmp/test-XXXXXX" )" - mkdir -p "${OUTPUT_DIR}" "${BUILD_DIR}" "${CACHE_DIR}" - export OUTPUT_DIR STD_OUT STD_ERR BUILD_DIR CACHE_DIR HOME BUILDPACK_DIR + mkdir -p "${OUTPUT_DIR}" "${BUILD_DIR}" "${CACHE_DIR}" "${ENV_DIR}" + export OUTPUT_DIR STD_OUT STD_ERR BUILD_DIR CACHE_DIR ENV_DIR BUILDPACK_DIR + export HOME - # We'll probably update PATH during the test, - # let's keep the original value safe - PREV_PATH="${PATH}" - export PREV_PATH + # We'll probably update PATH during the test, + # let's keep the original value safe + PREV_PATH="${PATH}" + export PREV_PATH - # Copy the buildpack code into BUILDPACK_DIR: - cp --archive "$( pwd )"/* "${BUILDPACK_DIR}/" + # Copy the buildpack code into BUILDPACK_DIR: + cp --archive "$( pwd )"/* "${BUILDPACK_DIR}/" - # Make sure we have a /app directory: - mkdir --parents "/app" + # Make sure we have a /app directory: + mkdir --parents "/app" } tearDown() { - # Set HOME back to its original value: - HOME="/root" - export HOME + # Set HOME back to its original value: + HOME="/root" + export HOME - # We may have changed working dir, let's switch back to the initial one: - popd > /dev/null 2>&1 + # We may have changed working dir, let's switch back to the initial one: + popd > /dev/null 2>&1 - # Set PATH back to its original value: - PATH="${PREV_PATH}" - export PATH + # Set PATH back to its original value: + PATH="${PREV_PATH}" + export PATH - # Remove /app: - rm --recursive --force "/app" + # Remove /app: + rm --recursive --force "/app" - # Remove OUTPUT_DIR, which also removes BUILD_DIR and CACHE_DIR: - rm --recursive --force "${OUTPUT_DIR}" + # Remove OUTPUT_DIR, which also removes BUILD_DIR and CACHE_DIR: + rm --recursive --force "${OUTPUT_DIR}" - # Remove BUILDPACK_DIR: - rm --recursive --force "${BUILDPACK_DIR}" + # Remove BUILDPACK_DIR: + rm --recursive --force "${BUILDPACK_DIR}" - unset BUILDPACK_DIR BUILD_DIR CACHE_DIR - unset JAVA_VERSION JAVA_WEBAPP_RUNNER_VERSION + unset BUILDPACK_DIR BUILD_DIR CACHE_DIR ENV_DIR + unset JAVA_VERSION JAVA_WEBAPP_RUNNER_VERSION } # Load shUnit2, which also run the tests: diff --git a/test/tests b/test/tests index 3938c07..f9d04f9 100755 --- a/test/tests +++ b/test/tests @@ -4,159 +4,168 @@ source "$( pwd )/test/utils" source "$( pwd )/test/helpers" -default_jvm_version="1.8" -default_tomcat_version="9.0.120.0" +case "${STACK}" in + scalingo-22) + default_jvm_version="1.8" + ;; + *) + default_jvm_version="25" + ;; +esac + +default_tomcat_version="9.0.121.0" + test::war::defaults() { - # Tests a deployment of a war app - # With default settings +# Tests a deployment of a war app +# With default settings - test::utils::setupFixture "war_defaults" - test::helpers::common_deploy "WAR" \ - "${default_jvm_version}." "${default_tomcat_version}" + test::utils::setupFixture "war_defaults" + test::helpers::common_deploy "WAR" \ + "${default_jvm_version}." "${default_tomcat_version}" } test::war::jvm1-8() { - # Tests a deployment of a war app - # Specifying we want JVM 1.8 via environment +# Tests a deployment of a war app +# Specifying we want JVM 1.8 via environment - JAVA_VERSION="1.8" - export JAVA_VERSION + JAVA_VERSION="1.8" + export JAVA_VERSION - test::utils::setupFixture "war_defaults" - test::helpers::common_deploy "WAR" \ - "${JAVA_VERSION}." "${default_tomcat_version}" + test::utils::setupFixture "war_defaults" + test::helpers::common_deploy "WAR" \ + "${JAVA_VERSION}." "${default_tomcat_version}" } test::war::jvm17() { - # Tests a deployment of a war app - # Specifying we want JVM 17 via environment +# Tests a deployment of a war app +# Specifying we want JVM 17 via environment - JAVA_VERSION="17" - export JAVA_VERSION + JAVA_VERSION="17" + export JAVA_VERSION - test::utils::setupFixture "war_defaults" - test::helpers::common_deploy "WAR" \ - "${JAVA_VERSION}." "${default_tomcat_version}" + test::utils::setupFixture "war_defaults" + test::helpers::common_deploy "WAR" \ + "${JAVA_VERSION}." "${default_tomcat_version}" } -test::war::jvm18() { - # Tests a deployment of a war app - # Specifying we want JVM 18 via environment +test::war::jvm21() { +# Tests a deployment of a war app +# Specifying we want JVM 21 via environment - JAVA_VERSION="18" - export JAVA_VERSION + JAVA_VERSION="21" + export JAVA_VERSION - test::utils::setupFixture "war_defaults" - test::helpers::common_deploy "WAR" \ - "${JAVA_VERSION}." "${default_tomcat_version}" + test::utils::setupFixture "war_defaults" + test::helpers::common_deploy "WAR" \ + "${JAVA_VERSION}." "${default_tomcat_version}" } -test::war::jvm19() { - # Tests a deployment of a war app - # Specifying we want JVM 19 via environment +test::war::jvm25() { +# Tests a deployment of a war app +# Specifying we want JVM 25 via environment - JAVA_VERSION="19" - export JAVA_VERSION + JAVA_VERSION="25" + export JAVA_VERSION - test::utils::setupFixture "war_defaults" - test::helpers::common_deploy "WAR" \ - "${JAVA_VERSION}." "${default_tomcat_version}" + test::utils::setupFixture "war_defaults" + test::helpers::common_deploy "WAR" \ + "${JAVA_VERSION}." "${default_tomcat_version}" } test::war::tomcat9() { - # Tests a deployment of a war app - # Specifying we want Tomcat 9.0.85.0 via environment +# Tests a deployment of a war app +# Specifying we want Tomcat 9.0.119.0 via environment - JAVA_WEBAPP_RUNNER_VERSION="9.0.85.0" - export JAVA_WEBAPP_RUNNER_VERSION + JAVA_WEBAPP_RUNNER_VERSION="9.0.119.0" + export JAVA_WEBAPP_RUNNER_VERSION - test::utils::setupFixture "war_defaults" - test::helpers::common_deploy "WAR" \ - "${default_jvm_version}." "${JAVA_WEBAPP_RUNNER_VERSION}" + test::utils::setupFixture "war_defaults" + test::helpers::common_deploy "WAR" \ + "${default_jvm_version}." "${JAVA_WEBAPP_RUNNER_VERSION}" } test::war::tomcat10() { - # Tests a deployment of a war app - # Specifying we want Tomcat 10.1.18.0 via environment - # Specifying we want JVM17 via environment (required for Tomcat 10). +# Tests a deployment of a war app +# Specifying we want Tomcat 10.1.56.0 via environment +# Specifying we want JVM 25 via environment (required for Tomcat 10). - JAVA_VERSION="17" - export JAVA_VERSION + JAVA_VERSION="25" + export JAVA_VERSION - JAVA_WEBAPP_RUNNER_VERSION="10.1.18.0" - export JAVA_WEBAPP_RUNNER_VERSION + JAVA_WEBAPP_RUNNER_VERSION="10.1.56.0" + export JAVA_WEBAPP_RUNNER_VERSION - test::utils::setupFixture "war_defaults" - test::helpers::common_deploy "WAR" \ - "${JAVA_VERSION}." "${JAVA_WEBAPP_RUNNER_VERSION}" + test::utils::setupFixture "war_defaults" + test::helpers::common_deploy "WAR" \ + "${JAVA_VERSION}." "${JAVA_WEBAPP_RUNNER_VERSION}" } test::war::war_in_rootdir() { - # Tests a deployment of a war app - # With the .war file in the root directory of the project +# Tests a deployment of a war app +# With the .war file in the root directory of the project - test::utils::setupFixture "war_defaults" - test::helpers::common_deploy "WAR" \ - "${default_jvm_version}." "${default_tomcat_version}" + test::utils::setupFixture "war_defaults" + test::helpers::common_deploy "WAR" \ + "${default_jvm_version}." "${default_tomcat_version}" } test::war::war_in_subir() { - # Tests a deployment of a war app - # With the .war file in a subdirectory of the project - # This must fail at the `bin/detect` step since we only look in the project - # root directory. +# Tests a deployment of a war app +# With the .war file in a subdirectory of the project +# This must fail at the `bin/detect` step since we only look in the project +# root directory. - test::utils::setupFixture "war_in_subdir" - test::utils::detect + test::utils::setupFixture "war_in_subdir" + test::utils::detect - test::utils::assertCapturedError - test::utils::assertCapturedEquals "no" + test::utils::assertCapturedError + test::utils::assertCapturedEquals "no" } test::war::war_in_rootdir_specified() { - # Tests a deployment of a war app - # Specifying the path to the .war file via environment - # With the .war file in the root directory of the project +# Tests a deployment of a war app +# Specifying the path to the .war file via environment +# With the .war file in the root directory of the project - WAR_PATH="sample.war" - export WAR_PATH + WAR_PATH="sample.war" + export WAR_PATH - test::utils::setupFixture "war_defaults" - test::helpers::common_deploy "WAR" \ - "${default_jvm_version}." "${default_tomcat_version}" + test::utils::setupFixture "war_defaults" + test::helpers::common_deploy "WAR" \ + "${default_jvm_version}." "${default_tomcat_version}" } test::war::war_in_subir_specified() { - # Tests a deployment of a war app - # Specifying the path to the .war file via environment - # With the .war file in a subdirectory of the project +# Tests a deployment of a war app +# Specifying the path to the .war file via environment +# With the .war file in a subdirectory of the project - WAR_PATH="subdir/sample.war" - export WAR_PATH + WAR_PATH="subdir/sample.war" + export WAR_PATH - test::utils::setupFixture "war_in_subdir" - test::helpers::common_deploy "WAR" \ - "${default_jvm_version}." "${default_tomcat_version}" + test::utils::setupFixture "war_in_subdir" + test::helpers::common_deploy "WAR" \ + "${default_jvm_version}." "${default_tomcat_version}" } test::war::no_war() { - # Tests a deployment of a war app - # Where there is no .war file! - # This must fail at the `bin/detect` step. +# Tests a deployment of a war app +# Where there is no .war file! +# This must fail at the `bin/detect` step. - test::utils::setupFixture "no_war" - test::utils::detect + test::utils::setupFixture "no_war" + test::utils::detect - test::utils::assertCapturedError - test::utils::assertCapturedEquals "no" + test::utils::assertCapturedError + test::utils::assertCapturedEquals "no" } suite_addTest test::war::defaults suite_addTest test::war::jvm1-8 suite_addTest test::war::jvm17 -suite_addTest test::war::jvm18 -suite_addTest test::war::jvm19 +suite_addTest test::war::jvm21 +suite_addTest test::war::jvm25 suite_addTest test::war::tomcat9 suite_addTest test::war::tomcat10 diff --git a/test/utils b/test/utils index f9299dd..b01fcac 100644 --- a/test/utils +++ b/test/utils @@ -4,639 +4,709 @@ # https://github.com/ryanbrainard/heroku-buildpack-testrunner/blob/master/lib/test_utils.sh test::utils::setupFixture() { - # Deploys a fixture by copying the fixture files in $BUILD_DIR. - # The fixture code must be stored in `BUILDPACK_DIR/test/fixtures/$fixture` - # - # Globals: - # BUILDPACK_DIR - # BUILD_DIR - # - # Arguments: - # $1 > $fixture: Name of the fixture. This is also the name of the - # directory that contains the code to be deployed. - - local fixture="${1}" - - cp --archive "${BUILDPACK_DIR}/test/fixtures/${fixture}"/. "${BUILD_DIR}/" +# +# Deploys a fixture by copying the fixture files in $BUILD_DIR. +# The fixture code must be stored in `BUILDPACK_DIR/test/fixtures/$fixture` +# +# Globals: +# BUILDPACK_DIR +# BUILD_DIR +# +# Arguments: +# $1 > $fixture: Name of the fixture. This is also the name of the +# directory that contains the code to be deployed. +# + + local fixture="${1}" + + cp --archive \ + "${BUILDPACK_DIR}/test/fixtures/${fixture}"/. \ + "${BUILD_DIR}/" } test::utils::detect() { - # Runs the `bin/detect` script of the buildpack. - # Outputs are captured to allow for further analysis. - # - # Globals: - # BUILDPACK_DIR - # BUILD_DIR +# +# Runs the `bin/detect` script of the buildpack. +# Outputs are captured to allow for further analysis. +# +# Globals: +# BUILDPACK_DIR +# BUILD_DIR +# - test::utils::capture "${BUILDPACK_DIR}/bin/detect" "${BUILD_DIR}" + test::utils::capture \ + "${BUILDPACK_DIR}/bin/detect" \ + "${BUILD_DIR}" } test::utils::compile() { - # Runs the `bin/compile` script of the buildpack. - # Outputs are captured to allow for further analysis. - # - # Globals: - # BUILDPACK_DIR - # BUILD_DIR - # CACHE_DIR - - test::utils::capture "${BUILDPACK_DIR}/bin/compile" "${BUILD_DIR}" \ - "${CACHE_DIR}" +# +# Runs the `bin/compile` script of the buildpack. +# Outputs are captured to allow for further analysis. +# +# Globals: +# BUILDPACK_DIR +# BUILD_DIR +# CACHE_DIR +# ENV_DIR +# + + test::utils::capture \ + "${BUILDPACK_DIR}/bin/compile" \ + "${BUILD_DIR}" "${CACHE_DIR}" "${ENV_DIR}" } test::utils::capture() { - # Runs the given command and captures its outputs: - # - redirects stdout to $STD_OUT - # - redirects stderr to $STD_ERR - # - stores the command in $LAST_COMMAND - # - stores the exit code of the command run in $RETURN - # - # All outputs (see Globals) are emptied prior to the command being run. - # - # Globals: - # LAST_COMMAND - # RETURN - # STD_OUT - # STD_ERR - # - # Arguments: - # $@ > $LAST_COMMAND: Command (with arguments) to run and for which - # outputs are captured. - - test::utils::resetCapture - - LAST_COMMAND="$@" - - "$@" >"${STD_OUT}" 2>"${STD_ERR}" - - RETURN="${?}" +# +# Runs the given command and captures its outputs: +# - redirects stdout to $STD_OUT +# - redirects stderr to $STD_ERR +# - stores the command in $LAST_COMMAND +# - stores the exit code of the command run in $RETURN +# +# All outputs (see Globals) are emptied prior to the command being run. +# +# Globals: +# LAST_COMMAND +# RETURN +# STD_OUT +# STD_ERR +# +# Arguments: +# $@ > $LAST_COMMAND: Command (with arguments) to run and for which +# outputs are captured. +# + + test::utils::resetCapture + + LAST_COMMAND="$@" + + "$@" >"${STD_OUT}" 2>"${STD_ERR}" + + RETURN="${?}" } test::utils::resetCapture() { - # Resets the globals used to capture the functions results: - # - removes the file pointed by $STD_OUT - # - removes the file pointed by $STD_ERR - # - unsets LAST_COMMAND - # - unsets RETURN - # - # Globals: - # LAST_COMMAND - # RETURN - # STD_OUT - # STD_ERR - - if [ -f "${STD_OUT}" ]; then - rm "${STD_OUT}" - fi - - if [ -f "${STD_ERR}" ]; then - rm "${STD_ERR}" - fi - - unset LAST_COMMAND - unset RETURN +# +# Resets the globals used to capture the functions results: +# - removes the file pointed by $STD_OUT +# - removes the file pointed by $STD_ERR +# - unsets LAST_COMMAND +# - unsets RETURN +# +# Globals: +# LAST_COMMAND +# RETURN +# STD_OUT +# STD_ERR +# + + if [ -f "${STD_OUT}" ]; then + rm "${STD_OUT}" + fi + + if [ -f "${STD_ERR}" ]; then + rm "${STD_ERR}" + fi + + unset LAST_COMMAND + unset RETURN } test::utils::assertSuccess() { - # Asserts that the last command run exited with a status code of 0. - # - # Globals: - # RETURN +# +# Asserts that the last command run exited with a status code of 0. +# +# Globals: +# RETURN +# - assertEquals \ - "Expected captured exit code to be 0; was <${RETURN}>" \ - "0" "${RETURN}" + assertEquals \ + "Expected captured exit code to be 0; was <${RETURN}>" \ + "0" "${RETURN}" } test::utils::assertCapturedEquals() { - # Asserts that the last command output equals the given string. - # - # Globals: - # STD_OUT - # - # Arguments: - # $1 > $expected: Text that is compared to the last command output and - # that must be the exact same. +# +# Asserts that the last command output equals the given string. +# +# Globals: +# STD_OUT +# +# Arguments: +# $1 > $expected: Text that is compared to the last command output and +# that must be the exact same. +# - local expected="${1}" + local expected="${1}" - assertEquals "${expected}" "$( cat "${STD_OUT}" )" + assertEquals "${expected}" "$( cat "${STD_OUT}" )" } test::utils::assertCapturedNotEquals() { - # Asserts that the last command output does not equal the given string. - # - # Globals: - # STD_OUT - # - # Arguments: - # $1 > $notExpected: Text that is compared to the last command output and - # that must not be the same. +# +# Asserts that the last command output does not equal the given string. +# +# Globals: +# STD_OUT +# +# Arguments: +# $1 > $notExpected: Text that is compared to the last command output and +# that must not be the same. +# - local notExpected="${1}" + local notExpected="${1}" - assertNotEquals "${notExpected}" "$( cat "${STD_OUT}" )" + assertNotEquals "${notExpected}" "$( cat "${STD_OUT}" )" } test::utils::assertCaptured() { - # Asserts that the last command output contains the given string. - # - # Globals: - # STD_OUT - # - # Arguments: - # $1 > $expected: Text that must be present in the last command output. +# +# Asserts that the last command output contains the given string. +# +# Globals: +# STD_OUT +# +# Arguments: +# $1 > $expected: Text that must be present in the last command output. +# - local expected="${1}" + local expected="${1}" - test:utils::assertFileContains "${expected}" "${STD_OUT}" + test:utils::assertFileContains "${expected}" "${STD_OUT}" } test::utils::assertNotCaptured() { - # Asserts that the last command output does not contain the given string. - # - # Globals: - # STD_OUT - # - # Arguments: - # $1 > $notExpected: Text that must not be present in the last command - # output. +# +# Asserts that the last command output does not contain the given string. +# +# Globals: +# STD_OUT +# +# Arguments: +# $1 > $notExpected: Text that must not be present in the last command +# output. +# - local notExpected="${1}" + local notExpected="${1}" - test::utils::assertFileNotContains "${notExpected}" "${STD_OUT}" + test::utils::assertFileNotContains "${notExpected}" "${STD_OUT}" } test::utils::assertCapturedSuccess() { - # Asserts that the last command succeeded and that stderr is empty. - # - # Globals: - # RETURN - # STD_ERR +# +# Asserts that the last command succeeded and that stderr is empty. +# +# Globals: +# RETURN +# STD_ERR +# - test::utils::assertSuccess + test::utils::assertSuccess - assertEquals \ - "Expected STD_ERR to be empty; was <$( cat "${STD_ERR}" )>" \ - "" "$( cat "${STD_ERR}" )" + assertEquals \ + "Expected STD_ERR to be empty; was <$( cat "${STD_ERR}" )>" \ + "" "$( cat "${STD_ERR}" )" } test::utils::assertCapturedError() { - # Asserts that the last command failed (return code was >0) - # Optionally also asserts that it failed with the given return code. - # Optionally also asserts that stderr contains the given error message. - # - # Globals: - # RETURN - # STD_ERR - # - # Arguments: - # $1 > $expectedErrorCode: (optional) Expected return code (must be > 0). - # - # $2 > $expectedErrorMsg: (optional) Expected error message (full or - # partial. - - if [ $# -gt 1 ]; then - local expectedErrorCode="${1}" - shift - fi - - local expectedErrorMsg="${1:-""}" - - if [ -z "${expectedErrorCode}" ]; then - assertTrue \ - "Expected captured exit code to be greater than 0; was <${RETURN}>" \ - "[ ${RETURN} -gt 0 ]" - else - assertTrue \ - "Expected captured exit code to be <${expectedErrorCode}>; was <${RETURN}>" \ - "[ ${RETURN} -eq ${expectedErrorCode} ]" - fi - - if [ "${expectedErrorMsg}" != "" ]; then - test::utils::assertFileContains \ - "Expected STD_ERR to contain error <${expectedErrorMsg}>" \ - "${expectedErrorMsg}" "${STD_ERR}" - fi +# +# Asserts that the last command failed (return code was >0) +# Optionally also asserts that it failed with the given return code. +# Optionally also asserts that stderr contains the given error message. +# +# Globals: +# RETURN +# STD_ERR +# +# Arguments: +# $1 > $expectedErrorCode: (optional) Expected return code (must be > 0). +# +# $2 > $expectedErrorMsg: (optional) Expected error message (full or +# partial. +# + + if [ $# -gt 1 ]; then + local expectedErrorCode="${1}" + shift + fi + + local expectedErrorMsg="${1:-""}" + + if [ -z "${expectedErrorCode}" ]; then + assertTrue \ + "Expected captured exit code to be greater than 0; was <${RETURN}>" \ + "[ ${RETURN} -gt 0 ]" + else + assertTrue \ + "Expected captured exit code to be <${expectedErrorCode}>; was <${RETURN}>" \ + "[ ${RETURN} -eq ${expectedErrorCode} ]" + fi + + if [ "${expectedErrorMsg}" != "" ]; then + test::utils::assertFileContains \ + "Expected STD_ERR to contain error <${expectedErrorMsg}>" \ + "${expectedErrorMsg}" "${STD_ERR}" + fi } test::utils::_assertContains() { - # Asserts that the given `$needle` is in the given `$haystack`. - # - # Arguments: - # $1 > $needle: Fixed string that is searched in `$haystack`. - # - # $2 > $haystack: Either a string or a file to search in. (See - # `$haystack_type` for further details). - # - # $3 > $expectation: (optional) Either `0` if `$needle` should be found - # in `$haystack` or `1` otherwise. Defaults to `0`. - # - # $4 > $haystack_type: (optional) Either "file" to search in a file or - # "text" to search in a variable. Defaults to "file". - - if [ ! $# -ge 2 ] && [ ! $# -le 4 ]; then - fail "Expected 2 to 4 parameters, $# given" - fi - - local needle="${1}" - local haystack="${2}" - local expectation="${3:0}" - local haystack_type="${4:-"file"}" - - if [ "${haystack_type}" = "text" ]; then - echo "${haystack}" \ - | grep --quiet --fixed-strings --regexp="${needle}" - else - grep --quiet --fixed-strings --regexp="${needle}" "${haystack}" - fi - - if [ "${expectation}" != "$?" ]; then - case "${expectation}" in - 0) - msg="Expected <${haystack}> to contain <${needle}>" - ;; - 1) - msg="Did not expect <${haystack}> to contain <${needle}>" - ;; - esac - - fail "${msg}" - fi +# +# Asserts that the given `$needle` is in the given `$haystack`. +# +# Arguments: +# $1 > $needle: Fixed string that is searched in `$haystack`. +# +# $2 > $haystack: Either a string or a file to search in. (See +# `$haystack_type` for further details). +# +# $3 > $expectation: (optional) Either `0` if `$needle` should be found +# in `$haystack` or `1` otherwise. Defaults to `0`. +# +# $4 > $haystack_type: (optional) Either "file" to search in a file or +# "text" to search in a variable. Defaults to "file". +# + + if [ ! $# -ge 2 ] && [ ! $# -le 4 ]; then + fail "Expected 2 to 4 parameters, $# given" + fi + + local needle="${1}" + local haystack="${2}" + local expectation="${3:0}" + local haystack_type="${4:-"file"}" + + if [[ "${haystack_type}" == "file" ]]; then + haystack="$( cat "${haystack}" )" + fi + + echo "${haystack}" \ + | grep --quiet --fixed-strings --regexp="${needle}" + + if [ "${expectation}" != "$?" ]; then + case "${expectation}" in + 0) + msg="Expected output to match <${needle}>" + ;; + 1) + msg="Did not expect output to match <${needle}>" + ;; + esac + + msg="${msg}\nOUTPUT:" + msg="${msg}\n${haystack}" + + fail "${msg}" + fi } test::utils::assertTextContains() { - # Asserts that the given `$needle` is in the given (text) `$haystack`. - # - # Shortcut for `_assertContains $needle $haystack 0 "text"` - # - # Arguments: - # $1 > $needle: Fixed string that is searched in `$haystack`. - # - # $2 > $haystack: String to search in. +# +# Asserts that the given `$needle` is in the given (text) `$haystack`. +# +# Shortcut for `_assertContains $needle $haystack 0 "text"` +# +# Arguments: +# $1 > $needle: Fixed string that is searched in `$haystack`. +# +# $2 > $haystack: String to search in. +# - local needle="${1}" - local haystack="${2}" + local needle="${1}" + local haystack="${2}" - test::utils::_assertContains "${needle}" "${haystack}" 0 "text" + test::utils::_assertContains "${needle}" "${haystack}" 0 "text" } test::utils::assertTextNotContains() { - # Asserts that the given `$needle` is not in the given (text) `$haystack`. - # - # Shortcut for `_assertContains $needle $haystack 1 "text"`. - # - # Arguments: - # $1 > $needle: Fixed string that is searched in `$haystack`. - # - # $2 > $haystack: String to search in. +# +# Asserts that the given `$needle` is not in the given (text) `$haystack`. +# +# Shortcut for `_assertContains $needle $haystack 1 "text"`. +# +# Arguments: +# $1 > $needle: Fixed string that is searched in `$haystack`. +# +# $2 > $haystack: String to search in. +# - local needle="${1}" - local haystack="${2}" + local needle="${1}" + local haystack="${2}" - test::utils::_assertContains "${needle}" "${haystack}" 1 "text" + test::utils::_assertContains "${needle}" "${haystack}" 1 "text" } test::utils::assertFileContains() { - # Asserts that the given `$needle` is in the given (file) `$haystack`. - # - # Shortcut for `_assertContains $needle $haystack 0 "file"`. - # - # Arguments: - # $1 > $needle: Fixed string that is searched in `$haystack`. - # - # $2 > $haystack: File to search in. +# +# Asserts that the given `$needle` is in the given (file) `$haystack`. +# +# Shortcut for `_assertContains $needle $haystack 0 "file"`. +# +# Arguments: +# $1 > $needle: Fixed string that is searched in `$haystack`. +# +# $2 > $haystack: File to search in. +# - local needle="${1}" - local haystack="${2}" + local needle="${1}" + local haystack="${2}" - test::utils::_assertContains "${needle}" "${haystack}" 0 "file" + test::utils::_assertContains "${needle}" "${haystack}" 0 "file" } test::utils::assertFileNotContains() { - # Asserts that the given `$needle` is not in the given (file) `$haystack`. - # - # Shortcut for `_assertContains $needle $haystack 1 "file"`. - # - # Arguments: - # $1 > $needle: Fixed string that is searched in `$haystack`. - # - # $2 > $haystack: File to search in. +# +# Asserts that the given `$needle` is not in the given (file) `$haystack`. +# +# Shortcut for `_assertContains $needle $haystack 1 "file"`. +# +# Arguments: +# $1 > $needle: Fixed string that is searched in `$haystack`. +# +# $2 > $haystack: File to search in. +# - local needle="${1}" - local haystack="${2}" + local needle="${1}" + local haystack="${2}" - test::utils::_assertContains "${needle}" "${haystack}" 1 "file" + test::utils::_assertContains "${needle}" "${haystack}" 1 "file" } test::utils::_assertMatches() { - # Asserts that the given `$needle` matches the given `$haystack`. - # - # Arguments: - # $1 > $needle: Regular expression that is searched in `$haystack`. - # - # $2 > $haystack: Either a string or a file to search in. (See - # `$haystack_type` for further details). - # - # $3 > $expectation: (optional) Either `0` if `$needle` should match - # `$haystack` or `1` otherwise. Defaults to `0`. - # - # $4 > $haystack_type: (optional) Either "file" to search in a file or - # "text" to search in a variable. Defaults to "file". - - if [ ! $# -ge 2 ] && [ ! $# -le 4 ]; then - fail "Expected 2 to 4 parameters, $# given" - fi - - local needle="${1}" - local haystack="${2}" - local expectation="${3:0}" - local haystack_type="${4:-"file"}" - - if [ "${haystack_type}" = "text" ]; then - echo "${haystack}" \ - | grep --quiet --extended-regexp --regexp="${needle}" - else - grep --quiet --extended-regexp --regexp="${needle}" "${haystack}" - fi - - if [ "${expectation}" != "$?" ]; then - case "${expectation}" in - 0) - msg="Expected <${haystack}> to match <${needle}>" - ;; - 1) - msg="Did not expect <${haystack}> to match <${needle}>" - ;; - esac - - fail "${msg}" - fi +# +# Asserts that the given `$needle` matches the given `$haystack`. +# +# Arguments: +# $1 > $needle: Regular expression that is searched in `$haystack`. +# +# $2 > $haystack: Either a string or a file to search in. (See +# `$haystack_type` for further details). +# +# $3 > $expectation: (optional) Either `0` if `$needle` should match +# `$haystack` or `1` otherwise. Defaults to `0`. +# +# $4 > $haystack_type: (optional) Either "file" to search in a file or +# "text" to search in a variable. Defaults to "file". +# + +if [ ! $# -ge 2 ] && [ ! $# -le 4 ]; then + fail "Expected 2 to 4 parameters, $# given" + fi + + local needle="${1}" + local haystack="${2}" + local expectation="${3:0}" + local haystack_type="${4:-"file"}" + + if [[ "${haystack_type}" == "file" ]]; then + haystack="$( cat "${haystack}" )" + fi + + echo "${haystack}" \ + | grep --quiet --extended-regexp --regexp="${needle}" + + if [ "${expectation}" != "$?" ]; then + case "${expectation}" in + 0) + msg="Expected output to match <${needle}>" + ;; + 1) + msg="Did not expect output to match <${needle}>" + ;; + esac + + msg="${msg}\nOUTPUT:" + msg="${msg}\n${haystack}" + + fail "${msg}" + fi } test::utils::assertFileMatches() { - local needle="${1}" - local haystack="${2}" + local needle="${1}" + local haystack="${2}" - test::utils::_assertMatches "${needle}" "${haystack}" 0 "file" + test::utils::_assertMatches "${needle}" "${haystack}" 0 "file" } test::utils::assertFileNotMatches() { - local needle="${1}" - local haystack="${2}" + local needle="${1}" + local haystack="${2}" - test::utils::_assertMatches "${needle}" "${haystack}" 1 "file" + test::utils::_assertMatches "${needle}" "${haystack}" 1 "file" } test::utils::assertTextMatches() { - local needle="${1}" - local haystack="${2}" + local needle="${1}" + local haystack="${2}" - test::utils::_assertMatches "${needle}" "${haystack}" 0 "text" + test::utils::_assertMatches "${needle}" "${haystack}" 0 "text" } test::utils::assertTextNotMatches() { - local needle="${1}" - local haystack="${2}" + local needle="${1}" + local haystack="${2}" - test::utils::_assertMatches "${needle}" "${haystack}" 1 "text" + test::utils::_assertMatches "${needle}" "${haystack}" 1 "text" } test::utils::assertFileStartswith() { - # Asserts that the given (file) `$haystack` starts with the given - # `$needle`. - # - # Shortcut for `_assertMatches $needle $haystack 0 "file"`. - # - # Arguments: - # $1 > $needle: Fixed string that is searched at the very beginning of - # `$haystack`. - # - # $2 > $haystack: File to search in. +# +# Asserts that the given (file) `$haystack` starts with the given +# `$needle`. +# +# Shortcut for `_assertMatches $needle $haystack 0 "file"`. +# +# Arguments: +# $1 > $needle: Fixed string that is searched at the very beginning of +# `$haystack`. +# +# $2 > $haystack: File to search in. +# - local needle="${1}" - local haystack="${2}" + local needle="${1}" + local haystack="${2}" - test::utils::assertFileMatches "^${needle}" "${haystack}" + test::utils::assertFileMatches "^${needle}" "${haystack}" } test::utils::assertFileNotStartswith() { - # Asserts that the given (file) `$haystack` does not start with the given - # `$needle`. - # - # Shortcut for `_assertMatches $needle $haystack 1 "file"`. - # - # Arguments: - # $1 > $needle: Fixed string that is searched at the very beginning of - # `$haystack`. - # - # $2 > $haystack: File to search in. +# +# Asserts that the given (file) `$haystack` does not start with the given +# `$needle`. +# +# Shortcut for `_assertMatches $needle $haystack 1 "file"`. +# +# Arguments: +# $1 > $needle: Fixed string that is searched at the very beginning of +# `$haystack`. +# +# $2 > $haystack: File to search in. +# - local needle="${1}" - local haystack="${2}" + local needle="${1}" + local haystack="${2}" - test::utils::assertFileNotMatches "^${needle}" "${haystack}" 1 "file" + test::utils::assertFileNotMatches "^${needle}" "${haystack}" 1 "file" } test::utils::assertTextStartswith() { - # Asserts that the given (text) `$haystack` starts with the given - # `$needle`. - # - # Shortcut for `_assertMatches $needle $haystack 0 "text"`. - # - # Arguments: - # $1 > $needle: Fixed string that is searched at the very beginning of - # `$haystack`. - # - # $2 > $haystack: Text to search in. +# +# Asserts that the given (text) `$haystack` starts with the given +# `$needle`. +# +# Shortcut for `_assertMatches $needle $haystack 0 "text"`. +# +# Arguments: +# $1 > $needle: Fixed string that is searched at the very beginning of +# `$haystack`. +# +# $2 > $haystack: Text to search in. +# - local needle="${1}" - local haystack="${2}" + local needle="${1}" + local haystack="${2}" - test::utils::assertTextMatches "^${needle}" "${haystack}" + test::utils::assertTextMatches "^${needle}" "${haystack}" } test::utils::assertTextNotStartswith() { - # Asserts that the given (text) `$haystack` does not start with the given - # `$needle`. - # - # Shortcut for `_assertMatches $needle $haystack 1 "text"`. - # - # Arguments: - # $1 > $needle: Fixed string that is searched at the very beginning of - # `$haystack`. - # - # $2 > $haystack: File to search in. +# +# Asserts that the given (text) `$haystack` does not start with the given +# `$needle`. +# +# Shortcut for `_assertMatches $needle $haystack 1 "text"`. +# +# Arguments: +# $1 > $needle: Fixed string that is searched at the very beginning of +# `$haystack`. +# +# $2 > $haystack: File to search in. +# - local needle="${1}" - local haystack="${2}" + local needle="${1}" + local haystack="${2}" - test::utils::assertTextNotMatches "^${needle}" "${haystack}" + test::utils::assertTextNotMatches "^${needle}" "${haystack}" } test::utils::assertCapturedStartswith() { - # Asserts that the last command output starts with the given `$needle`. - # - # Shortcut for `assertFileStartswith $needle $STD_OUT`. - # - # Globals: - # STD_OUT - # - # Arguments: - # $1 > $needle: Fixed string that is searched in `$STD_OUT`. +# +# Asserts that the last command output starts with the given `$needle`. +# +# Shortcut for `assertFileStartswith $needle $STD_OUT`. +# +# Globals: +# STD_OUT +# +# Arguments: +# $1 > $needle: Fixed string that is searched in `$STD_OUT`. +# - local needle="${1}" + local needle="${1}" - test::utils::assertFileStartswith "${needle}" "${STD_OUT}" + test::utils::assertFileStartswith "${needle}" "${STD_OUT}" } test::utils::assertCapturedMatches() { - # - # - # +# +# +# - local needle="${1}" + local needle="${1}" - test::utils::assertFileMatches "${needle}" "${STD_OUT}" + test::utils::assertFileMatches "${needle}" "${STD_OUT}" } test::utils::assertErrorMatches() { - # - # - # +# +# +# - local needle="${1}" + local needle="${1}" - test::utils::assertFileMatches "${needle}" "${STD_ERR}" + test::utils::assertFileMatches "${needle}" "${STD_ERR}" } test::utils::command_exists () { - # Asserts that the given command exists. - # - # Arguments: - # $1: Name of the command that should be present. +# +# Asserts that the given command exists. +# +# Arguments: +# $1: Name of the command that should be present. +# - type "${1}" > /dev/null 2>&1 ; + type "${1}" > /dev/null 2>&1 ; } test::utils::assertFileMD5() { - # Asserts that the given file md5 checksum matches the actual one. - # - # Arguments: - # $1 > $expectedHash: Expected md5 hash. - # - # $2 > $filename: Path to the file for which we want to check the md5 - # hash. - - local expectedHash="${1}" - local filename="${2}" - - if test::utils::command_exists "md5sum"; then - md5_cmd="md5sum ${filename}" - expected_md5_cmd_output="${expectedHash} ${filename}" - elif test::utils::command_exists "md5"; then - md5_cmd="md5 ${filename}" - expected_md5_cmd_output="MD5 (${filename}) = ${expectedHash}" - else - fail "no suitable MD5 hashing command found on this system" - fi - - assertEquals "${expected_md5_cmd_output}" "$( "${md5_cmd}" )" +# +# Asserts that the given file md5 checksum matches the actual one. +# +# Arguments: +# $1 > $expectedHash: Expected md5 hash. +# +# $2 > $filename: Path to the file for which we want to check the md5 +# hash. +# + + local expectedHash="${1}" + local filename="${2}" + + if test::utils::command_exists "md5sum"; then + md5_cmd="md5sum ${filename}" + expected_md5_cmd_output="${expectedHash} ${filename}" + elif test::utils::command_exists "md5"; then + md5_cmd="md5 ${filename}" + expected_md5_cmd_output="MD5 (${filename}) = ${expectedHash}" + else + fail "no suitable MD5 hashing command found on this system" + fi + + assertEquals "${expected_md5_cmd_output}" "$( "${md5_cmd}" )" } test::utils::assertDirectoryExists() { - # Asserts that the given path exists and is a directory. - # - # Arguments: - # $1 > $dir_path: Fixed string that is searched at the very beginning of - # `$haystack`. +# +# Asserts that the given path exists and is a directory. +# +# Arguments: +# $1 > $dir_path: Fixed string that is searched at the very beginning of +# `$haystack`. +# - local dir_path="${1}" + local dir_path="${1}" - if [[ ! -e "${dir_path}" ]]; then - fail "${dir_path} does not exist" - fi + if [[ ! -e "${dir_path}" ]]; then + fail "${dir_path} does not exist" + fi - if [[ ! -d "${dir_path}" ]]; then - fail "${dir_path} is not a directory" - fi + if [[ ! -d "${dir_path}" ]]; then + fail "${dir_path} is not a directory" + fi } test::utils::assertFileExists() { - # Asserts that the given path exists and is a file. - # - # Arguments: - # $1 > $file_path: Path to the file that must exist. +# +# Asserts that the given path exists and is a file. +# +# Arguments: +# $1 > $file_path: Path to the file that must exist. +# - local file_path="${1}" + local file_path="${1}" - if [[ ! -e "${file_path}" ]]; then - fail "${file_path} does not exist" - fi + if [[ ! -e "${file_path}" ]]; then + fail "${file_path} does not exist" + fi - if [[ ! -f "${file_path}" ]]; then - fail "${file_path} is not a file" - fi + if [[ ! -f "${file_path}" ]]; then + fail "${file_path} is not a file" + fi } test::utils::assertFileDoesNotExist() { - # Asserts that the given path does not exist. - # - # Arguments: - # $1 > $file_path: Path that must not exist. +# +# Asserts that the given path does not exist. +# +# Arguments: +# $1 > $file_path: Path that must not exist. +# - local file_path="${1}" + local file_path="${1}" - assertTrue "${file_path} exists" "[[ ! -e "${file_path}" ]]" + assertTrue "${file_path} exists" "[[ ! -e "${file_path}" ]]" } test::utils::enter_prod() { - # Helper to switch to a production-like environment: - # - $HOME is set to /app - # - Working directory is set to $HOME - # - Content of $BUILD_DIR is copied to $HOME - # - $HOME/.profile.d/*.sh scripts are sourced - # - # This environment is automatically reverted back, thanks to the `tearDown` - # function override in `test/run`. - # It would have been nice to put that in a `setUp` override. Unfortunately, - # we can't because the switch of environment should only happen **during** - # the test (after several steps have been reached), not before. - # - # /!\ This function is meant to be called after a successful call to - # to `test::utils::compile` to further test the result of the - # bin/compile call. It makes no sense to call it in another context. - # - # /!\ This function should be completed with instructions to adjust the - # PATH environment variable. These depend on the buildpack though, so - # we can't make something generic. - # Notice that you should have a `test::helpers::_set_path` function - # available for this purpose. - # - # Globals: - # HOME - # BUILD_DIR - - # Change home directory: - HOME="/app" - export HOME - - pushd "${HOME}" > /dev/null - - # Copy BUILD_DIR content to /app: - cp --archive --recursive "${BUILD_DIR}"/. "${HOME}/" - - # Source .profile.d scripts: - for p in "${HOME}/.profile.d"/*.sh; do - if [ -r "${p}" ] && [ ! -k "${p}" ]; then - source "${p}" - fi - done - - unset p - - # Adjust PATH if `test::helpers::_set_path` exists: - declare -f -F "test::helpers::_set_path" >/dev/null \ - && test::helpers::_set_path +# +# Helper to switch to a production-like environment: +# - $HOME is set to /app +# - Working directory is set to $HOME +# - Content of $BUILD_DIR is copied to $HOME +# - $HOME/.profile.d/*.sh scripts are sourced +# +# This environment is automatically reverted back, thanks to the `tearDown` +# function override in `test/run`. +# It would have been nice to put that in a `setUp` override. Unfortunately, +# we can't because the switch of environment should only happen **during** +# the test (after several steps have been reached), not before. +# +# /!\ This function is meant to be called after a successful call to +# to `test::utils::compile` to further test the result of the +# bin/compile call. It makes no sense to call it in another context. +# +# /!\ This function should be completed with instructions to adjust the +# PATH environment variable. These depend on the buildpack though, so +# we can't make something generic. +# Notice that you should have a `test::helpers::_set_path` function +# available for this purpose. +# +# Globals: +# HOME +# BUILD_DIR +# + + # Change home directory: + HOME="/app" + export HOME + + pushd "${HOME}" > /dev/null + + # Copy BUILD_DIR content to /app: + cp --archive --recursive "${BUILD_DIR}"/. "${HOME}/" + + # Source .profile.d scripts: + for p in "${HOME}/.profile.d"/*.sh; do + if [ -r "${p}" ] && [ ! -k "${p}" ]; then + source "${p}" + fi + done + + unset p + + # Adjust PATH if `test::helpers::_set_path` exists: + declare -f -F "test::helpers::_set_path" >/dev/null \ + && test::helpers::_set_path } From c7e8ea2465c2dd7ef2cb78ad999af0ff8862e25b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20KUBLER?= Date: Thu, 3 Sep 2026 15:41:58 +0200 Subject: [PATCH 3/9] chore(cmnlib): use cmnlib 20260901 --- vendor/cmnlib.sh | 1041 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1041 insertions(+) create mode 100644 vendor/cmnlib.sh diff --git a/vendor/cmnlib.sh b/vendor/cmnlib.sh new file mode 100644 index 0000000..0cee545 --- /dev/null +++ b/vendor/cmnlib.sh @@ -0,0 +1,1041 @@ +#!/usr/bin/env bash +# +# Please see https://github.com/Scalingo/buildpack-cmnlib for help. +# +# Conventions: +# +# - Functions prefixed with `_cmn__` are designed for internal use only. +# They shouldn't be used outside of cmnlib. +# +# - Functions prefixed with `cmn::` are designed for public use. +# They are meant to be used in buildpacks code. +# +# - Variables starting with `_CMN_` are for internal use only. +# They shouldn't be used outside of cmnlib. +# + + +_CMN_VERSION_=20260901 + +# If _CMN_LOADED_ is set, this means the library is already sourced. +# As functions are readonly, we don't want to load it again, as this would +# cause failures. +# So, if _CMN_LOADED_ is set, return immediately. +# Else, set it and load the functions. +[[ -n "${_CMN_LOADED_:-}" ]] && return +_CMN_LOADED_="yes" + + +_cmn__read_lines() { +# +## Internal only +# +# Redirects input to stdin, line by line. +# This allows the `cmn::output::` functions to support heredoc. +# + + if (($#)); then + printf '%s\n' "$@" + elif [[ ! -t 0 ]]; then + # stdin is not a terminal, we can safely call `cat` without arguments. + # Removing this conditional will make `cat` wait for an input on stdin, + # which will never happend, hence causing the script to hang forever. + # + # This redirects stdin to stdout. + cat + fi +} + +_cmn__output_emit() { +# +## Internal only +# +# Reads input line by line thanks to `_cmn__read_lines` +# and outputs each line formatted on the appropriate file descriptor. +# + + local -r prefix="${1}"; shift + # Use 1 for stdout, 2 for stderr + # Defaults to stdout: + local -r fd="${1:-1}" + shift || true + + # shellcheck disable=SC2312 + while IFS= read -r line; do + printf '%s%s\n' "${prefix}" "${line}" >&"${fd}" + done < <( _cmn__read_lines "$@" ) +} + +_cmn__main_err() { +# +## Internal only +# +# Handler for unmanaged errors. +# Please use `cmn::main::finish` or `cmn::main::fail` instead. +# + + # We don't want to be caught in an err loop: + # so stop trapping ERR ASAP: + set +o errexit + trap - ERR + + local -r code="${1:-1}" + local -r cmd="${2:-""}" + + cmn::task::fail + + cmn::output::err <<-EOM + Caught Error: + Command: ${cmd} + Exit: ${code} + EOM + + cmn::output::traceback + + exit "${code}" +} + +_cmn__main_end() { +# +## Internal only +# +# Handler for EXIT signal. +# Please use `cmn::main::finish` or `cmn::main::fail` instead. +# + + _cmn__trap_teardown + + # Ensure we are back in build_dir: + if [[ -n "${build_dir:-}" && -d "${build_dir}" ]]; then + pushd "${build_dir}" > /dev/null || true + fi + + # Remove tmp_dir, unless _CMN_DEBUG_ is set: + if [[ -z "${_CMN_DEBUG_:-}" && -n "${tmp_dir:-}" && -d "${tmp_dir}" ]] + then + rm -rf -- "${tmp_dir}" || true + fi +} + +_cmn__trap_setup() { +# +## Internal only +# +# Instructs the buildpack to catch the `SIGHUP`, `SIGINT`, `SIGQUIT`, +# `SIGABRT`, and `SIGTERM` signals and to call `cmn::main::fail` +# when it happens. +# Also instructs the buildpack to catch `EXIT` and to call `_cmn__main_end` +# when it happens. +# + + trap '_cmn__main_err $? "$BASH_COMMAND"' ERR + trap '_cmn__main_err 129 "SIGHUP"' HUP + trap '_cmn__main_err 130 "SIGINT"' INT + trap '_cmn__main_err 131 "SIGQUIT"' QUIT + trap '_cmn__main_err 134 "SIGABRT"' ABRT + trap '_cmn__main_err 143 "SIGTERM"' TERM + + trap "_cmn__main_end" EXIT +} + +_cmn__trap_teardown() { +# +## Internal only +# +# Instructs the buildpack to stop catching the `EXIT`, `SIGHUP`, `SIGINT`, +# `SIGQUIT`, `SIGABRT`, and `SIGTERM` signals. +# + + trap - EXIT ERR HUP INT QUIT ABRT TERM +} + + + +cmn::output::info() { +# +# Outputs an informational message on stdout. +# Can be called with a string argument or with a Bash heredoc. +# + + local -r prefix=" " + _cmn__output_emit "${prefix}" 1 "${@}" +} + +cmn::output::warn() { +# +# Outputs a warning message on stdout. +# Can be called with a string argument or with a Bash heredoc. +# + + local -r prefix=" ! " + _cmn__output_emit "${prefix}" 1 "${@}" +} + +cmn::output::err() { +# +# Outputs an error message on stderr. +# Can be called with a string argument or with a Bash heredoc. +# + + local -r prefix=" !! " + _cmn__output_emit "${prefix}" 2 "${@}" + + if [[ -n "${_CMN_DEBUG_:-}" ]]; then + cmn::output::traceback + fi +} + +# shellcheck disable=SC2120 +cmn::output::debug() { +# +# Outputs a debug message on stdout. +# Can be called with a string argument or with a Bash heredoc. +# Only outputs when _CMN_DEBUG_ is set! +# +# Setting _CMN_DEBUG_ should be reserved for cmnlib itself, +# or when debugging buildpacks. +# +# Since providing args is optional, disable SC2120. + + # Return ASAP if _CMN_DEBUG_ isn't set + [[ -z "${_CMN_DEBUG_:-}" ]] && return + + # Return to line if we are in a task to avoid breaking the output: + if [[ -n "${_CMN_IN_TASK_:-}" ]]; then + printf -- "\n" + unset _CMN_IN_TASK_ + fi + + # shellcheck disable=SC2312 + while IFS= read -r line; do + printf " * %s: %s: %s: %s\n" \ + "${BASH_SOURCE[1]}" \ + "${FUNCNAME[1]}" \ + "${BASH_LINENO[0]}" \ + "${line}" + done < <( _cmn__read_lines "${@}" ) +} + +cmn::output::traceback() { +# +# Outputs a traceback to stderr. +# + + printf "\n !! Traceback:\n" >&2 + + for (( i=1; i<${#FUNCNAME[@]}; i++ )); do + >&2 printf " !! %s: %s: %s\n" \ + "${BASH_SOURCE[i]}" \ + "${FUNCNAME[${i}]}" \ + "${BASH_LINENO[${i}-1]}" + done +} + + + +cmn::main::start() { +# +# Configures Bash options, populates a few global variables and marks the +# beginning of the buildpack. +# +# Use this function at the beginning of the buildpack. +# + + set -o errexit -o errtrace -o pipefail + + if [[ -n "${BUILDPACK_DEBUG:-}" ]]; then + set -o xtrace + fi + + build_dir="${2:-}" + cache_dir="${3:-}" + env_dir="${4:-}" + + base_dir="$( cd -P "$( dirname "${1}" )" && pwd )" + buildpack_dir="$( readlink -f "${base_dir}/.." )" + tmp_dir="$( mktemp --directory --tmpdir="/tmp" --quiet \ + "buildpack-XXXXXX" )" + + readonly build_dir + readonly cache_dir + readonly env_dir + readonly base_dir + readonly buildpack_dir + readonly tmp_dir + + cmn::output::debug <<-EOM + build_dir: ${build_dir} + cache_dir: ${cache_dir} + env_dir: ${env_dir} + buildpack_dir: ${buildpack_dir} + tmp_dir: ${tmp_dir} + EOM + + pushd "${build_dir}" > /dev/null + + _cmn__trap_setup +} + +cmn::main::finish() { +# +# Outputs a success message and exits with a `0` return code, thus +# instructing the platform that the buildpack ran successfully. +# +# Use this function as the last instruction of the buildpack, when it +# succeeded. +# + + printf "\n%s\n" "All done." + exit 0 +} + + +cmn::main::fail() { +# +# Outputs an error message if given and exits with the given return code, thus +# instructing the platform that the buildpack failed (and so did the +# build). +# +# When no return code is given, defaults to 1. +# +# Use this function to end the buildpack, when it encountered an unrecoverable +# failure. +# + + local -r code="${1:-1}" + shift + + cmn::task::fail + cmn::output::err "${@}" + + exit "${code}" +} + +cmn::step::start() { +# +# Outputs a message marking the beginning of a buildpack step. A step is a +# group of tasks that are logically bound. +# Use this function when the step is about to start. +# + + printf -- "--> %s\n" "${*}" +} + + + +cmn::task::start() { +# +# Outputs a message marking the beginning of a buildpack task. A task is a +# single instruction, such as downloading a file, extracting an archive,... +# Use this function when the task is about to start. +# + + _CMN_IN_TASK_="yes" + printf -- " %s... " "$*" +} + +cmn::task::finish() { +# +# Outputs a success message marking the end of a task. +# Use this function when the task succeeded. +# + + if [[ -n "${_CMN_IN_TASK_:-}" ]]; then + printf -- "%s\n" "OK." + unset _CMN_IN_TASK_ + fi +} + +# shellcheck disable=SC2120 +cmn::task::fail() { +# +# Outputs an error message marking the end of a task. +# Calls `cmn::output::err` with `$1` when `$1` is set. +# +# Since providing args is optional, disable SC2120. +# + + if [[ -n "${_CMN_IN_TASK_:-}" ]]; then + printf -- "%s\n" "Failed." + unset _CMN_IN_TASK_ + fi + + if [[ -n "${1:-}" ]]; then + cmn::output::err "${1}" + fi +} + + + +_cmn__inventory_get() { +# +# Given a specific version, retrieves the corresponding given field from the +# inventory file. +# When version is not set, retrieves the field for the version set as the +# default one in the inventory. +# +# $1: inventory file +# $2: field to retrieve. Must be one of "version", "url" or "checksum" +# $3: (opt) version to retrieve. If not set, retrieves the field for the +# default version, if any. +# +# Returns: +# 0: value found +# 1: no matching entry found +# 2: invalid arguments or unreadable inventory +# + + local inventory_file="${1}" + local field="${2}" + local wanted_version="${3:-}" + local version + local url + local checksum + local default + + # Check we have a valid $field: + case "${field}" in + version|url|checksum) + # These values are OK, do nothing + ;; + *) + return 2 + ;; + esac + + # Check inventory file is readable: + [[ -r "${inventory_file}" ]] || return 2 + + # Read inventory file line by line and map columns to variables + # shellcheck disable=SC2034 + while IFS=$'\t' read -r version url checksum default; do + # Skip comments and blank lines + [[ -z "${version}" || "${version}" == \#* ]] && continue + + if [[ -n "${wanted_version}" ]]; then + # Skip instructions and go on with the next line + # if current version is not the one we're looking for: + [[ "${version}" == "${wanted_version}" ]] || continue + else + # Skip instructions and go on with the next line + # if current row is not the default one: + [[ "${default}" == "default" ]] || continue + fi + + printf '%s\n' "${!field}" + return 0 + done < "${inventory_file}" + + # If we reach this line, we haven't found what we're looking for: + return 1 +} + +cmn::inventory::get_default() { +# +# Retrieves the default version from the inventory file. +# + + local -r inventory_file="${1}" + + _cmn__inventory_get \ + "${inventory_file}" \ + "version" +} + +cmn::inventory::get_url() { +# +# Given a specific version, retrieves the corresponding URL from the given +# inventory file. +# +# $1: inventory file to search +# $2: wanted version, defaults to version set as default in inventory +# + + local -r inventory_file="${1}" + local -r wanted_version="${2:-}" + + # Return if no version was specified and we weren't able to retrieve a + # default one. + [[ -n "${wanted_version}" ]] || return 2 + + _cmn__inventory_get \ + "${inventory_file}" \ + "url" \ + "${wanted_version}" +} + +cmn::inventory::get_checksum() { +# +# Given a specific version, retrieves the corresponding checksum from the given +# inventory file. +# +# $1: inventory file to search +# $2: wanted version, defaults to version set as default in inventory +# + + local -r inventory_file="${1}" + local -r wanted_version="${2:-}" + + # Return if no version was specified and we weren't able to retrieve a + # default one. + [[ -n "${wanted_version}" ]] || return 2 + + _cmn__inventory_get \ + "${inventory_file}" \ + "checksum" \ + "${wanted_version}" +} + + + +_cmn__file_read_checksum() { +# +# Reads file hash from the given checksum file. +# Output format is :. +# +# Tips: use `cmn::file::validate_checksum` directly. +# +# $1: checksum file +# + + local -r file="${1}" + + local -r hash_algo="${file##*.}" + local hash + + # Reads the whole first line of $file + # Ensure this command never provokes an exit. + # (`read` returns 1 when the file misses a newline at EOF) + IFS= read -r line < "${file}" || true + + # Trim starting whitespaces: + line=${line#"${line%%[![:space:]]*}"} + + # Retrieves the string before the first whitespace: + hash=${line%%[[:space:]]*} + + printf "%s:%s\n" "${hash_algo}" "${hash}" +} + +cmn::file::validate_checksum() { +# +# Computes the checksum of a file and checks that it matches the one stored in +# the reference file. +# md5, sha1, sha256, and sha512 hashing algorithm are currently supported. +# +# $1: file +# $2: checksum file OR checksum +# + + local -r file="${1}" + local hash="${2}" + + local rc=1 + + # Check if given hash is a file. + # If so, we have to read it first and extract the reference hash from it. + if [[ -f "${hash}" ]]; then + hash="$( _cmn__file_read_checksum "${hash}" )" + fi + + # Use Bash parameter expansion to split $hash in 2 parts: + # $hash_algo = longest match from beginning to a ':' + # $ref_hash = longest match from ':' to the end. + local -r hash_algo="${hash%%:*}" + local -r ref_hash="${hash##*:}" + + case "${hash_algo}" in + "sha1") + shasum --algorithm 1 --check --status <<< "${ref_hash} ${file}" + rc="${?}" + ;; + + "sha256") + shasum --algorithm 256 --check --status <<< "${ref_hash} ${file}" + rc="${?}" + ;; + + "sha512") + shasum --algorithm 512 --check --status <<< "${ref_hash} ${file}" + rc="${?}" + ;; + + "md5") + md5sum --check --status <<< "${ref_hash} ${file}" + rc="${?}" + ;; + + *) + rc=3 + ;; + esac + + cmn::output::debug <<-EOM + file: ${file} + hash: ${hash} + hash_algo: ${hash_algo} + ref_hash: ${ref_hash} + result: ${rc} + EOM + + return "${rc}" +} + +cmn::file::download() { +# +# Downloads the file pointed by the given URL and stores it at the given path. +# +# $1: URL of the file to download +# $2: (opt) Path where to output the downloaded file. Defaults to /dev/stdout. +# + + local -r url="${1}" + local -r out="${2:-"-"}" + + cmn::output::debug <<-EOM + Downloading: ${url} + Saving to: ${out} + EOM + + curl --silent --fail --location \ + --retry 3 --retry-delay 10 --retry-connrefused \ + --connect-timeout 10 --max-time 300 \ + --create-dirs --output "${out}" \ + "${url}" + + return "${?}" +} + +cmn::file::download_and_check() { +# +# Downloads a file from the specified URL, stores it at the specified path. +# Also downloads the checksum from the specified URL, stores it at the +# specified path. +# Finally checks the hash of the downloaded file against the downloaded +# checksum. +# +# $1: file URL +# $2: checksum URL +# $3: file path (where to store the downloaded file) +# $4: hash path (where to store the downloaded checksum file) +# + + local -r file_url="${1}" + local -r hash_url="${2}" + local -r file_path="${3}" + local -r hash_path="${4}" + + local rc=1 + + cmn::file::download "${file_url}" "${file_path}" & + cmn::file::download "${hash_url}" "${hash_path}" & + + cmn::jobs::wait + cmn::file::validate_checksum "${file_path}" "${hash_path}" + rc="${?}" + + return "${rc}" +} + + + +cmn::s3::upload() { +# +# Uploads a local file to a S3-compatible storage bucket. +# +# $1: Path to the local file to upload +# $2: Name of the bucket where to upload the file +# $3: Key of the object (name of the file in the remote bucket) +# + + local rc + local output + local -r file="${1}" + local -r bucket="${2}" + local -r key="${3#/}" # Removes any leading slash + local -r dest="s3://${bucket}/${key}" + + if [[ ! -f "${file}" ]]; then + printf "Unable to upload '%s': " \ + "file is not local.\n" \ + "${file}" >&2 + rc=2 + else + output="$( aws s3 cp \ + "${file}" \ + "${dest}" \ + --acl public-read \ + 2>&1 )" + + rc="${?}" + if (( rc != 0 )); then + printf "%s\n" "${output}" >&2 + fi + fi + + return "${rc}" +} + +cmn::s3::download() { +# +# Downloads a file from an S3-compatible storage bucket. +# +# $1: Name of the bucket where the file is stored +# $2: Key of the object (name of the file in the remote bucket) +# $3: Path to the local file +# + + local rc + local output + local -r bucket="${1}" + local -r key="${2#/}" # Removes any leading slash + local -r file="${3}" + local -r source="s3://${bucket}/${key}" + + local -r dir="$( dirname -- "${file}" )" + + if [[ ! -d "${dir}" ]]; then + printf "Unable to download file to '%s': " \ + "parent directory doesn't exist.\n" \ + "${dir}" >&2 + rc=2 + else + output="$( aws s3 cp \ + "${source}" \ + "${file}" \ + 2>&1 )" + + rc="${?}" + if (( rc != 0 )); then + printf "%s\n" "${output}" >&2 + fi + fi + + return "${rc}" +} + +cmn::s3::list_bucket() { +# +# Lists the content of the given bucket. +# Optionally limits the output to objects matching the given prefix. +# Output is in JSON. +# +# Note: +# Using `s3api` is a requirement to have a JSON output, +# which is more suitable for scripting purposes. +# +# $1: Name of the bucket to list +# $2: (opt): Prefix +# + + local -r bucket="${1}" + local -r prefix="${2:-}" + + aws s3api list-objects-v2 \ + --bucket "${bucket}" \ + --prefix "${prefix}" \ + --no-paginate +} + + + +cmn::jobs::wait() { +# +# Waits for all child jobs running in background to finish. +# Returns the number of failed jobs (zero means they all succeeded) +# +# We use `jobs -pr` to get the list of child jobs running in background. +# There might a very small risk of trying to wait for a process that would be +# already done when calling `wait` and another one taking the same pid. +# In this case, `wait` should fail, so it shouldn't be an issue. +# + + local rc=0 + local pid + + # shellcheck disable=SC2312 + while read -r pid; do + # If $pid is empty, skip to next loop item: + [[ -z "${pid}" ]] && continue + + if ! wait "${pid}"; then + (( rc+=1 )) + fi + done < <( jobs -pr ) + + return "${rc}" +} + + + +cmn::env::read() { +# +# Exports configuration variables of a buildpack's ENV_DIR to environment +# variables. +# +# Only configuration variables which names pass the positive pattern and don't +# match the negative pattern are exported. +# + + local -r envdir="${1}" + local e + local value + local env_vars + + env_vars="$( cmn::env::list "${envdir}" )" + + [[ -n "${env_vars}" ]] || return 0 + + while IFS= read -r e; do + # Read env var value from file: + value="$( <"${envdir}/${e}" )" + # Remove potential ending new line: + value="${value%$'\n'}" + # Export the env var: + export "${e}=${value}" + done <<< "${env_vars}" +} + +cmn::env::list() { +# +# List environment variables names from ENV_DIR. +# A few specific ones are voluntarily ignored. +# + + local -r env_dir="${1}" + + # Use an associative array to store the names of the environment variables + # we don't want to list from env_dir. + # This associative array is used as a set of forbidden values. + # The value (1) of each item is irrevelant, we only care about the keys. + # Using this data structure allows us to check if a value exists + # with a complexity of O(1). + # + # Same as: + # blocked[PATH]=1 + # blocked[GIT_DIR]=1 + # blocked[CPATH]=1 + # ... + # + local -A blocked=( + [PATH]=1 [GIT_DIR]=1 [CPATH]=1 [CPPATH]=1 + [LD_PRELOAD]=1 [LIBRARY_PATH]=1 [LD_LIBRARY_PATH]=1 + [JAVA_OPTS]=1 [JAVA_TOOL_OPTIONS]=1 + [BUILDPACK_URL]=1 [BUILD_DIR]=1 + ) + + local f + local name + + # List all content of env_dir: + for f in "${env_dir}"/*; do + # Skip item if not a file: + [[ -f "${f}" ]] || continue + + # Keep file name only + # For example: f="/app/env/MY_VAR" --> name="MY_VAR" + name="${f##*/}" + + # Skip if not a valid name: + [[ "${name}" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || continue + + # Skip if in blocked: + [[ -n "${blocked[${name}]:-}" ]] && continue + + printf '%s\n' "${name}" + done +} + + + +cmn::bp::run() { +# +# Downloads and runs a buildpack. +# + + local -r builddir="${1}" + local -r cachedir="${2}" + local -r envdir="${3}" + local -r tmpdir="${4}" + local -r url="${5}" + local -r branch="${6:-""}" + + local rc=0 + local bpdir + local bpout + local tech="" + local clone_args=(--quiet --depth=1) + + if ! bpdir="$( mktemp --directory --tmpdir="${tmpdir}" \ + --quiet "buildpack-XXXXXX" )" + then + cmn::main::fail 2 <<-EOM + Unable to create temporary directory to store the buildpack. + Aborting. + EOM + fi + + if [[ "${url}" =~ \.tgz$ || "${url}" =~ \.tar\.gz$ ]]; then + + cmn::task::start "Downloading buildpack" + local archive="${bpdir}/${url##*/}" + + # We want to handle cmn::file::download failures. + # shellcheck disable=SC2310 + if ! cmn::file::download "${url}" "${archive}"; then + cmn::main::fail "${?}" <<-EOM + Unable to download the buildpack from ${url}. + Common errors include but are not limited to: + - Temporary network issue. + - Typo in the provided ULR. + - Using a URL that requires authentication. + EOM + fi + cmn::task::finish + + cmn::task::start "Extracting buildpack code" + tar --extract --gzip --directory "${bpdir}" --file "${archive}" \ + --strip-components 1 >/dev/null 2>&1 + cmn::task::finish + else + cmn::task::start "Cloning buildpack" + + if [[ -n "${branch}" ]]; then + clone_args+=(--branch "${branch}") + fi + + # If the repo is not reachable, GIT_TERMINAL_PROMPT=0 allows us to fail + # instead of asking for credentials + if ! GIT_TERMINAL_PROMPT=0 \ + git clone "${clone_args[@]}" "${url}" "${bpdir}" 2>/dev/null + then + cmn::main::fail "${?}" <<-EOM + Unable to clone the buildpack from ${url}. + Common errors include but are not limited to: + - Temporary network issue. + - Typo in the Git URL. + - Using a private repository. + EOM + fi + cmn::task::finish + + if [[ -f "${bpdir}/.gitmodules" ]]; then + cmn::task::start "Initializing submodule" + pushd "${bpdir}" > /dev/null + git submodule update --init --recursive 2>/dev/null + popd > /dev/null + cmn::task::finish + fi + + if [[ -n "${branch}" ]]; then + cmn::task::start "Switching to branch ${branch}" + pushd "${bpdir}" > /dev/null + git checkout --quiet "${branch}" + popd > /dev/null + cmn::task::finish + fi + fi + + pushd "${bpdir}" > /dev/null + + # Ensure bin/detect and bin/compile are executable: + chmod --silent +x "${bpdir}/bin/"{detect,compile} + + cmn::task::start "Detecting technology" + if ! tech="$( "${bpdir}/bin/detect" "${builddir}" )"; then + cmn::main::fail 2 <<-EOM + Application is not compatible with the buildpack. + Please see our documentation about buildpacks for more information. + You can also reach out to our Support Team. + https://doc.scalingo.com/platform/deployment/buildpacks/intro + EOM + fi + cmn::task::finish + + cmn::output::info "Detected technology: ${tech}" + + cmn::task::start "Compiling" + if bpout="$( "${bpdir}/bin/compile" \ + "${builddir}" "${cachedir}" "${envdir}" 2>&1 )" + then + # Do nothing + # This syntax allows us to capture $? in the else block + : + else + cmn::main::fail "${?}" <<-EOM + An error occured while running the buildpack. + Here is the output: + ${bpout} + EOM + fi + cmn::task::finish + + # Source potential left-behind export script. + # This allows to leave a clean environment for the next buildpack. + if [[ -e "${bpdir}/export" ]]; then + cmn::task::start "Sourcing export script for next buildpack" + # shellcheck disable=SC1091 + source "${bpdir}/export" + cmn::task::finish + fi + + if [[ -x "${bpdir}/bin/release" ]]; then + "${bpdir}/bin/release" "${builddir}" \ + > "${builddir}/last_pack_release.out" + fi + + popd > /dev/null + + # We really don't want this step to be blocking or causing errors: + if [[ -z "${_CMN_DEBUG_:-}" && -n "${bpdir:-}" && -d "${bpdir}" ]]; then + rm -rf -- "${bpdir}" || true + fi + + return 0 +} + + + +readonly -f cmn::output::info +readonly -f cmn::output::warn +readonly -f cmn::output::err +readonly -f cmn::output::debug +readonly -f cmn::output::traceback + +readonly -f cmn::main::start +readonly -f cmn::main::finish +readonly -f cmn::main::fail + +readonly -f cmn::step::start + +readonly -f cmn::task::start +readonly -f cmn::task::finish +readonly -f cmn::task::fail + +readonly -f cmn::inventory::get_default +readonly -f cmn::inventory::get_url +readonly -f cmn::inventory::get_checksum + +readonly -f cmn::file::validate_checksum +readonly -f cmn::file::download +readonly -f cmn::file::download_and_check + +readonly -f cmn::s3::upload +readonly -f cmn::s3::download +readonly -f cmn::s3::list_bucket + +readonly -f cmn::jobs::wait + +readonly -f cmn::env::read +readonly -f cmn::env::list + +readonly -f cmn::bp::run + +readonly -f _cmn__read_lines +readonly -f _cmn__output_emit +readonly -f _cmn__main_err +readonly -f _cmn__main_end +readonly -f _cmn__trap_setup +readonly -f _cmn__trap_teardown +readonly -f _cmn__inventory_get +readonly -f _cmn__file_read_checksum From 13fe69004dbece10cbf97a0975a0f944209d756b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20KUBLER?= Date: Thu, 3 Sep 2026 16:12:23 +0200 Subject: [PATCH 4/9] chore: remove useless 'lib/commonsh' library --- .gitmodules | 0 lib/common.sh | 14 -------------- 2 files changed, 14 deletions(-) delete mode 100644 .gitmodules delete mode 100644 lib/common.sh diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29..0000000 diff --git a/lib/common.sh b/lib/common.sh deleted file mode 100644 index f216c23..0000000 --- a/lib/common.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -export_env_dir() { - env_dir=$1 - whitelist_regex=${2:-''} - blacklist_regex=${3:-'^(PATH|GIT_DIR|CPATH|CPPATH|LD_PRELOAD|LIBRARY_PATH|JAVA_OPTS|JAVA_TOOL_OPTIONS)$'} - if [ -d "$env_dir" ]; then - for e in $(ls $env_dir); do - echo "$e" | grep -E "$whitelist_regex" | grep -qvE "$blacklist_regex" && - export "$e=$(cat $env_dir/$e)" - : - done - fi -} From 66744686f4921343347cca9ea78a215596526864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20KUBLER?= Date: Thu, 3 Sep 2026 16:12:43 +0200 Subject: [PATCH 5/9] feat(ci): add ShellCheck to CI --- .github/workflows/ci.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a067d2..ee4222a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,22 +12,34 @@ permissions: contents: read jobs: + shellcheck: + name: "ShellCheck" + runs-on: ubuntu-24.04 + steps: + - uses: Scalingo/actions/shellcheck@main + with: + severity: style + files: | + bin/* + unit-tests: name: "Unit Tests (${{ matrix.stack }})" - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: stack: - scalingo-22 + - scalingo-24 + - scalingo-26 container: image: "scalingo/${{ matrix.stack }}:latest" env: STACK: "${{ matrix.stack }}" steps: - - name: 🛎 Checkout - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: 🔎 Run tests + - name: Run tests run: | test/run ... From a69710724415ce20082134f4ede08263870d6a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20KUBLER?= Date: Thu, 3 Sep 2026 16:57:13 +0200 Subject: [PATCH 6/9] chore(CI): use sha to identify version of GitHub action to use --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee4222a..443cb9c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: name: "ShellCheck" runs-on: ubuntu-24.04 steps: - - uses: Scalingo/actions/shellcheck@main + - uses: Scalingo/actions/shellcheck@8c4d8c7b3d03b58f44873ba2e281f1c2de2a3381 with: severity: style files: | From 986540a246c3bbfac168108362198c6a342b746c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20KUBLER?= Date: Thu, 3 Sep 2026 17:03:03 +0200 Subject: [PATCH 7/9] fix(ci): ShellCheck cmnlib --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 443cb9c..da3f294 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: with: severity: style files: | + vendor/cmnlib.sh bin/* unit-tests: From bb53174f7f3be9a114d410e443ac54c57d669bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20KUBLER?= Date: Thu, 3 Sep 2026 22:39:25 +0200 Subject: [PATCH 8/9] feat(readme): update README file --- README.md | 65 +++++++++++++++++++++++++------------------------------ 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 2503248..1922bbb 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,50 @@ -# buildpack: Java WAR +# Java War Buildpack -This is a [buildpack](http://doc.scalingo.com/buildpacks) for WAR file. +Run a Java Web Application Archive (`.war` file) on Scalingo using this +buildpack. -## Usage -### Detection +## Documentation -If the `WAR_PATH` environment variable is set, this buildpack checks if a .war -file exists where `WAR_PATH` points. If the file does actually exist, it is -used and served as the application. Otherwise, the buildpack fails. +See our official documentation: +👉 https://doc.scalingo.com/platform/deployment/deploy-java-jar-war -If `WAR_PATH` is not set, the buildpack falls back to checking if a .war file -exists at the root of the project and uses it if it does exist. +This buildpack first deploys a Java JDK into your container. -If you want to use a .war file stored at the root of your application, we -advise to unset the `WAR_PATH` environment variable and let the platform do its -magic. +It then deploys a standalone CLI tool named **Webapp Runner**. Webapp +Runner is based on the Apache Tomcat "pure Java" HTTP web server. It allows to +run a WAR application on Scalingo. -### Deployment Workflow -During the *`BUILD`* phase, this buildpack: +## Default Supported Version -1. Downloads and installs the Java Runtime Environment. -2. Downloads and installs a [webapp-runner](https://github.com/heroku/webapp-runner). -3. Validates the build. +The default Java JDK deployed by this buildpack depends on your [stack]: -:tada: This process results into a scalable image, ready to be packaged into a -container. +- For `scalingo-22`: OpenJDK 1.8 +- For `scalingo-24`: OpenJDK 25 +- For `scalingo-26`: OpenJDK 25 -### Environment +The default Webapp Runner deployed by this buildpack is defined in one of the +[inventory] files of the buildpack. -The following environment variables are available for you to tweak your -deployment: -#### `JAVA_VERSION` +## Maintenance Status -Version of the Java Runtime Environment to deploy.\ -Defaults to `1.8` +This buildpack is maintained by Scalingo solely for the deployment assets and +integration guidance provided in this repository and its associated +documentation. -#### `JAVA_WEBAPP_RUNNER_VERSION` +Applying Java and Webapp Runner upgrades, as well as security patches remains +the responsibility of the customer by updating the corresponding environment +variables and redeploying the application. -Version of the webapp-runner (Tomcat) to install and use.\ -Defaults to `9.0.120.0` -#### `WEBAPP_RUNNER_VERSION` +Should Scalingo discontinue maintenance of this buildpack or no longer +recommend its use, a notice period of at least six months will be provided +whenever feasible, except where immediate action is required due to security +concerns or external constraints. -**Deprecated**, please use [`JAVA_WEBAPP_RUNNER_VERSION`](#java_webapp_runner_version). -#### `WAR_PATH` +[inventory]: INVENTORY-default.tsv?plain=1 -Path to the .war file you want to run.\ -When unset, the platform tries to run a .war file at the root of the -application.\ -Defaults to being unset +[stack]: https://doc.scalingo.com/platform/internals/stacks/overview From c6749b242669aef915716245a71ecee42dd0080a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20KUBLER?= Date: Thu, 3 Sep 2026 22:42:53 +0200 Subject: [PATCH 9/9] fix(readme): typo and wording --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1922bbb..0499c32 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Java War Buildpack +# Java WAR Buildpack Run a Java Web Application Archive (`.war` file) on Scalingo using this buildpack. @@ -9,11 +9,11 @@ buildpack. See our official documentation: 👉 https://doc.scalingo.com/platform/deployment/deploy-java-jar-war -This buildpack first deploys a Java JDK into your container. +This buildpack first deploys a Java Development Kit (JDK) into your container. It then deploys a standalone CLI tool named **Webapp Runner**. Webapp -Runner is based on the Apache Tomcat "pure Java" HTTP web server. It allows to -run a WAR application on Scalingo. +Runner is based on the Apache Tomcat HTTP web server. It allows to run a WAR +application on Scalingo. ## Default Supported Version