include/buildpack.bash (187 lines of code) (raw):

_envfile-parse() { declare desc="Parse input into shell export commands" local key local value while read -r line || [[ -n "$line" ]]; do [[ "$line" =~ ^#.* ]] && continue [[ "$line" =~ ^$ ]] && continue key=${line%%=*} key=${key#*export } value="${line#*=}" case "$value" in \'*|\"*) value="${value}" ;; *) value=\""${value}"\" ;; esac echo "export ${key}=${value}" done <<< "$(cat)" } _move-build-to-app() { shopt -s dotglob nullglob # shellcheck disable=SC2086 rm -rf ${app_path:?}/* # build_path defined in outer scope # shellcheck disable=SC2086,SC2154 mv $build_path/* $app_path shopt -u dotglob nullglob } _select-buildpack() { if [[ -n "$BUILDPACK_URL" ]]; then title "Fetching custom buildpack" # buildpack_path defined in outer scope # shellcheck disable=SC2154 selected_path="$buildpack_path/custom" rm -rf "$selected_path" IFS='#' read -r url commit <<< "$BUILDPACK_URL" buildpack-install "$url" "$commit" custom &> /dev/null # unprivileged_user & unprivileged_group defined in outer scope # shellcheck disable=SC2154 chown -R "$unprivileged_user:$unprivileged_group" "$buildpack_path/custom" selected_name="$(unprivileged "$selected_path/bin/detect" "$build_path" || true)" else local buildpacks=($buildpack_path/*) local valid_buildpacks=() for buildpack in "${buildpacks[@]}"; do unprivileged "$buildpack/bin/detect" "$build_path" &> /dev/null \ && valid_buildpacks+=("$buildpack") done if [[ ${#valid_buildpacks[@]} -gt 1 ]]; then title "Warning: Multiple default buildpacks reported the ability to handle this app. The first buildpack in the list below will be used." echo "Detected buildpacks: $(sed -e "s:/tmp/buildpacks/[0-9][0-9]_buildpack-::g" <<< "${valid_buildpacks[@]}")" | indent fi if [[ ${#valid_buildpacks[@]} -gt 0 ]]; then selected_path="${valid_buildpacks[0]}" selected_name=$(unprivileged "$selected_path/bin/detect" "$build_path") fi fi if [[ "$selected_path" ]] && [[ "$selected_name" ]]; then title "$selected_name app detected" else title "Unable to select a buildpack" exit 1 fi } buildpack-build() { declare desc="Build an application using installed buildpacks" ensure-paths [[ "$USER" ]] || randomize-unprivileged buildpack-setup > /dev/null buildpack-execute | indent procfile-types | indent } buildpack-install() { declare desc="Install buildpack from Git URL and optional committish" declare url="$1" commit="$2" name="$3" ensure-paths if [[ ! "$url" ]]; then asset-cat include/buildpacks.txt | while read -r name url commit; do buildpack-install "$url" "$commit" "$name" done return fi # buildpack_path is defined in outer scope # shellcheck disable=SC2154 local target_path="$buildpack_path/${name:-$(basename "$url")}" if [[ "$(git ls-remote "$url" &> /dev/null; echo $?)" -eq 0 ]]; then if [[ "$commit" ]]; then if ! git clone --branch "$commit" --quiet --depth 1 "$url" "$target_path" &>/dev/null; then # if the shallow clone failed partway through, clean up and try a full clone rm -rf "$target_path" git clone "$url" "$target_path" cd "$target_path" || return 1 git checkout --quiet "$commit" cd - > /dev/null || return 1 else echo "Cloning into '$target_path'..." fi else git clone --depth=1 "$url" "$target_path" fi else local tar_args case "$url" in *.tgz|*.tar.gz) target_path="${target_path//.tgz}" target_path="${target_path//.tar.gz}" tar_args="-xzC" ;; *.tbz|*.tar.bz) target_path="${target_path//.tbz}" target_path="${target_path//.tar.bz}" tar_args="-xjC" ;; *.tar) target_path="${target_path//.tar}" tar_args="-xC" ;; esac echo "Downloading '$url' into '$target_path'..." mkdir -p "$target_path" curl -s --retry 2 "$url" | tar "$tar_args" "$target_path" chown -R root:root "$target_path" chmod 755 "$target_path" fi } buildpack-list() { declare desc="List installed buildpacks" ensure-paths ls -1 "$buildpack_path" } buildpack-setup() { # Buildpack expectations # app_path defined in outer scope # shellcheck disable=SC2154 export APP_DIR="$app_path" # shellcheck disable=SC2154 export HOME="$app_path" export REQUEST_ID="build-$RANDOM" export STACK="${STACK:-heroku-18}" # build_path defined in outer scope # shellcheck disable=SC2154 cp -r "$app_path/." "$build_path" # Prepare dropped privileges # unprivileged_user defined in outer scope # shellcheck disable=SC2154 usermod --home "$HOME" "$unprivileged_user" > /dev/null 2>&1 # vars defined in outer scope # shellcheck disable=SC2154 chown -R "$unprivileged_user:$unprivileged_group" \ "$app_path" \ "$build_path" \ "$cache_path" \ "$env_path" \ "$buildpack_path" # Useful settings / features export CURL_CONNECT_TIMEOUT="30" export CURL_TIMEOUT="180" # Buildstep backwards compatibility if [[ -f "$app_path/.env" ]]; then # shellcheck disable=SC2046 eval $(cat "$app_path/.env" | _envfile-parse) fi } buildpack-execute() { _select-buildpack cd "$build_path" || return 1 unprivileged "$selected_path/bin/compile" "$build_path" "$cache_path" "$env_path" if [[ -f "$selected_path/bin/release" ]]; then unprivileged "$selected_path/bin/release" "$build_path" "$cache_path" > "$build_path/.release" fi if [[ -f "$build_path/.release" ]]; then config_vars="$(cat "$build_path/.release" | yaml-get config_vars)" if [[ "$config_vars" ]]; then mkdir -p "$build_path/.profile.d" OIFS=$IFS IFS=$'\n' for var in $config_vars; do echo "export $(echo "$var" | sed -e 's/=/="/' -e 's/$/"/')" >> "$build_path/.profile.d/00_config_vars.sh" done IFS=$OIFS fi fi cd - > /dev/null || return 1 _move-build-to-app } buildpack-test() { declare desc="Build and run tests for an application using installed buildpacks" ensure-paths [[ "$USER" ]] || randomize-unprivileged buildpack-setup > /dev/null _select-buildpack if [[ ! -f "$selected_path/bin/test-compile" ]] || [[ ! -f "$selected_path/bin/test" ]]; then echo "Selected buildpack does not support test feature" exit 1 fi cd "$build_path" || return 1 chmod 755 "$selected_path/bin/test-compile" unprivileged "$selected_path/bin/test-compile" "$build_path" "$cache_path" "$env_path" cd "$app_path" || return 1 _move-build-to-app procfile-load-profile chmod 755 "$selected_path/bin/test" unprivileged "$selected_path/bin/test" "$app_path" "$env_path" }