diff --git a/Dockerfile b/Dockerfile index f598c523099..dead7581088 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,11 @@ RUN mkdir -p /etc/apt/keyrings \ && NIXPACKS_BIN_DIR=/usr/bin BIN_DIR=/usr/bin /tmp/nixpacks.bash \ && test -x /usr/bin/nixpacks \ && rm -rf /tmp/nixpacks.bash \ + && curl -o /tmp/railpack.bash -sSL https://railpack.com/install.sh \ + && chmod +x /tmp/railpack.bash \ + && RAILPACK_BIN_DIR=/usr/bin BIN_DIR=/usr/bin /tmp/railpack.bash \ + && test -x /usr/bin/railpack \ + && rm -rf /tmp/railpack.bash \ && echo "dokku dokku/hostname string $DOKKU_HOSTNAME" | debconf-set-selections \ && echo "dokku dokku/skip_key_file boolean $DOKKU_SKIP_KEY_FILE" | debconf-set-selections \ && echo "dokku dokku/vhost_enable boolean $DOKKU_VHOST_ENABLE" | debconf-set-selections \ diff --git a/docker/etc/sudoers.d/dokku-docker b/docker/etc/sudoers.d/dokku-docker index bebd885389b..f3dc76faa3c 100644 --- a/docker/etc/sudoers.d/dokku-docker +++ b/docker/etc/sudoers.d/dokku-docker @@ -1 +1 @@ -dokku ALL=NOPASSWD:SETENV:/usr/bin/docker,/usr/bin/docker-container-healthchecker,/usr/bin/docker-image-labeler,/usr/bin/lambda-builder,/usr/bin/nixpacks,/usr/bin/pack +dokku ALL=NOPASSWD:SETENV:/usr/bin/docker,/usr/bin/docker-container-healthchecker,/usr/bin/docker-image-labeler,/usr/bin/lambda-builder,/usr/bin/nixpacks,/usr/bin/railpack,/usr/bin/pack diff --git a/docker/usr/local/bin/railpack b/docker/usr/local/bin/railpack new file mode 100644 index 00000000000..35bee71d2fe --- /dev/null +++ b/docker/usr/local/bin/railpack @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $TRACE ]] && set -x + +main() { + declare desc="re-runs railpack commands as sudo" + local RAILPACK_BIN="" + if [[ -x "/usr/bin/railpack" ]]; then + RAILPACK_BIN="/usr/bin/railpack" + fi + + if [[ -z "$RAILPACK_BIN" ]]; then + echo "! No railpack binary found" 1>&2 + exit 1 + fi + + sudo -E "$RAILPACK_BIN" "$@" +} + +main "$@" diff --git a/docs/appendices/file-formats/railpack-json.md b/docs/appendices/file-formats/railpack-json.md new file mode 100644 index 00000000000..d6e390fb410 --- /dev/null +++ b/docs/appendices/file-formats/railpack-json.md @@ -0,0 +1,3 @@ +# railpack.json + +The `railpack.json` file is used to configure an application when built with the `railpack` builder. Please refer to the [railpack.json documentation](https://railpack.com/config/file) for more information. diff --git a/docs/deployment/builders/railpack.md b/docs/deployment/builders/railpack.md new file mode 100644 index 00000000000..dc0d634fb70 --- /dev/null +++ b/docs/deployment/builders/railpack.md @@ -0,0 +1,172 @@ +# Railpack + +> [!IMPORTANT] +> New as of 0.32.0 + +The `railpack` builder builds apps via [Railpack](https://railpack.com/), a buildpack alternative. + +## Usage + +### Requirements + +Before using Railpacks, the following steps must be taken: + +- Install the `railpack` cli: The `railpack` cli tool is not included by default with Dokku or as a dependency. It must also be installed as shown on [this page](https://railpack.com/installation). +- Create a `buildkit` builder: Railpack uses buildkit. + ```shell + docker run --rm --privileged -d --name buildkit moby/buildkit + ``` +- Set the buildkit builder: Update the `/etc/default/dokku` file to set `BUILDKIT_HOST`: + ```shell + touch /etc/default/dokku + echo "export BUILDKIT_HOST='docker-container://buildkit'" >> /etc/default/dokku + ```` + +### Detection + +This builder will be auto-detected in the following case: + +- A `railpack.json` exists in the root of the app repository. + +The builder may also be selected via the `builder:set` command + +```shell +dokku builder:set node-js-app selected railpack +``` + +### Supported languages + +See the [upstream railpack documentation](https://railpack.com/) for further information on what languages and frameworks are supported. + +### Build-time configuration variables + +For security reasons - and as per [Docker recommendations](https://github.com/docker/docker/issues/13490) - railpack-based deploys have variables available only during runtime. + +For users that require customization in the `build` phase, you may use build arguments via the [docker-options plugin](/docs/advanced-usage/docker-options.md). All environment variables set by the `config` plugin are automatically exported within the railpack build environment, and thus `--env` only requires setting a key without a value. + +```shell +dokku docker-options:add node-js-app build '--env NODE_ENV' +``` + +Alternatively, a full value may be provided in the form of `--env KEY=VALUE`: + +```shell +dokku docker-options:add node-js-app build '--env NODE_ENV=production' +``` + +### Changing the `railpack.json` location + +The `railpack.json` is expected to be found in a specific directory, depending on the deploy approach: + +- The `WORKDIR` of the Docker image for deploys resulting from `git:from-image` and `git:load-image` commands. +- The root of the source code tree for all other deploys (git push, `git:from-archive`, `git:sync`). + +Sometimes it may be desirable to set a different path for a given app, e.g. when deploying from a monorepo. This can be done via the `railpackjson-path` property: + +```shell +dokku builder-railpack:set node-js-app railpackjson-path .dokku/railpack.json +``` + +The value is the path to the desired file *relative* to the base search directory, and will never be treated as absolute paths in any context. If that file does not exist within the repository, the build will fail. + +The default value may be set by passing an empty value for the option: + +```shell +dokku builder-railpack:set node-js-app railpackjson-path +``` + +The `railpackjson-path` property can also be set globally. The global default is `railpack.json`, and the global value is used when no app-specific value is set. + +```shell +dokku builder-railpack:set --global railpackjson-path railpack2.json +``` + +The default value may be set by passing an empty value for the option. + +```shell +dokku builder-railpack:set --global railpackjson-path +``` + +### Disabling cache + +Cache is enabled by default, but can be disabled by setting the `no-cache` property to `true`: + +```shell +dokku builder-railpack:set node-js-app no-cache true +``` + +The default value may be set by passing an empty value for the option: + +```shell +dokku builder-railpack:set node-js-app no-cache +``` + +The `no-cache` property can also be set globally. The global default is `false`, and the global value is used when no app-specific value is set. + +```shell +dokku builder-railpack:set --global no-cache true +``` + +The default value may be set by passing an empty value for the option. + +```shell +dokku builder-railpack:set --global no-cache +``` + +### Displaying builder-railpack reports for an app + +You can get a report about the app's storage status using the `builder-railpack:report` command: + +```shell +dokku builder-railpack:report +``` + +``` +=====> node-js-app builder-railpack information + Builder-railpack computed railpackjson path: railpack2.json + Builder-railpack global railpackjson path: railpack.json + Builder-railpack railpackjson path: railpack2.json + Builder-railpack computed no cache: true + Builder-railpack global no cache: false + Builder-railpack no cache: true +=====> python-sample builder-railpack information + Builder-railpack computed railpackjson path: railpack.json + Builder-railpack global railpackjson path: railpack.json + Builder-railpack railpackjson path: + Builder-railpack computed no cache: false + Builder-railpack global no cache: false + Builder-railpack no cache: +=====> ruby-sample builder-railpack information + Builder-railpack computed railpackjson path: railpack.json + Builder-railpack global railpackjson path: railpack.json + Builder-railpack railpackjson path: + Builder-railpack computed no cache: false + Builder-railpack global no cache: false + Builder-railpack no cache: +``` + +You can run the command for a specific app also. + +```shell +dokku builder-railpack:report node-js-app +``` + +``` +=====> node-js-app builder-railpack information + Builder-railpack computed railpackjson path: railpack2.json + Builder-railpack global railpackjson path: railpack.json + Builder-railpack railpackjson path: railpack2.json + Builder-railpack computed no cache: true + Builder-railpack global no cache: false + Builder-railpack no cache: true +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku builder-railpack:report node-js-app --builder-railpack-no-cache +``` + +``` +true +``` diff --git a/docs/template.html b/docs/template.html index d81802c40dd..9e26b37590f 100644 --- a/docs/template.html +++ b/docs/template.html @@ -150,6 +150,7 @@ Lambda Builder Nixpacks Builder Null Builder + Railpack Builder Deployment Methods Git Deployment diff --git a/plugins/builder-railpack/builder-build b/plugins/builder-railpack/builder-build new file mode 100755 index 00000000000..e0716522d6e --- /dev/null +++ b/plugins/builder-railpack/builder-build @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/builder-railpack/internal-functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-builder-railpack-builder-build() { + declare desc="builder-railpack builder-build plugin trigger" + declare trigger="builder-build" + declare BUILDER_TYPE="$1" APP="$2" SOURCECODE_WORK_DIR="$3" + + if [[ "$BUILDER_TYPE" != "railpack" ]]; then + return + fi + + dokku_log_info1 "Building $APP from railpack" + + if ! command -v "railpack" &>/dev/null; then + dokku_log_fail "Missing railpack, install it" + fi + + local IMAGE=$(get_app_image_name "$APP") + local DOCKER_BUILD_LABEL_ARGS=("--label=dokku" "--label=org.label-schema.schema-version=1.0" "--label=org.label-schema.vendor=dokku" "--label=com.dokku.image-stage=build" "--label=com.dokku.builder-type=railpack" "--label=com.dokku.app-name=$APP") + + pushd "$SOURCECODE_WORK_DIR" &>/dev/null + + plugn trigger pre-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR" + + no_cache="$(fn-builder-railpack-computed-no-cache "$APP")" + RAILPACK_ARGS="" + if [[ "$no_cache" == "true" ]]; then + RAILPACK_ARGS="$RAILPACK_ARGS --no-cache" + fi + + local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$BUILDER_TYPE") + DOCKER_ARGS+=$(: | plugn trigger docker-args-process-build "$APP" "$BUILDER_TYPE") + + # strip --link, --volume and -v args from DOCKER_ARGS + local DOCKER_ARGS=$(sed -e "s/^--link=[[:graph:]]\+[[:blank:]]\?//g" -e "s/^--link[[:blank:]]\?[[:graph:]]\+[[:blank:]]\?//g" -e "s/^--volume=[[:graph:]]\+[[:blank:]]\?//g" -e "s/^--volume[[:blank:]]\?[[:graph:]]\+[[:blank:]]\?//g" -e "s/^-v[[:blank:]]\?[[:graph:]]\+[[:blank:]]\?//g" <<<"$DOCKER_ARGS") + declare -a ARG_ARRAY + eval "ARG_ARRAY=($DOCKER_ARGS)" + + eval "$(config_export app "$APP" --merged)" + + if [[ -f "$SOURCECODE_WORK_DIR/Procfile" ]]; then + if procfile-util exists --process-type release; then + procfile-util delete --process-type release + fi + fi + + # shellcheck disable=SC2086 + if ! railpack build "${ARG_ARRAY[@]}" $RAILPACK_ARGS --name "$IMAGE-build" "$SOURCECODE_WORK_DIR"; then + dokku_log_warn "Failure building image" + return 1 + fi + + if ! suppress_output "$DOCKER_BIN" image build -f "$PLUGIN_AVAILABLE_PATH/builder-railpack/dockerfiles/builder-build.Dockerfile" --build-arg APP_IMAGE="$IMAGE-build" "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS -t "$IMAGE" "$SOURCECODE_WORK_DIR"; then + "$DOCKER_BIN" image remove "$IMAGE-build" + dokku_log_warn "Failure injecting docker labels and custom entrypoint on image" + return 1 + fi + + "$DOCKER_BIN" image remove "$IMAGE-build" + + plugn trigger post-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR" + popd &>/dev/null || pushd "/tmp" >/dev/null +} + +trigger-builder-railpack-builder-build "$@" diff --git a/plugins/builder-railpack/builder-detect b/plugins/builder-railpack/builder-detect new file mode 100755 index 00000000000..1ba0b89baf6 --- /dev/null +++ b/plugins/builder-railpack/builder-detect @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +source "$PLUGIN_AVAILABLE_PATH/config/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-builder-railpack-builder-detect() { + declare desc="builder-railpack builder-detect plugin trigger" + declare trigger="builder-detect" + declare APP="$1" SOURCECODE_WORK_DIR="$2" + + if [[ -f "$SOURCECODE_WORK_DIR/railpack.json" ]]; then + echo "railpack" + return + fi +} + +trigger-builder-railpack-builder-detect "$@" diff --git a/plugins/builder-railpack/builder-release b/plugins/builder-railpack/builder-release new file mode 100755 index 00000000000..7c3650421b7 --- /dev/null +++ b/plugins/builder-railpack/builder-release @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-builder-railpack-builder-release() { + declare desc="builder-railpack builder-release plugin trigger" + declare trigger="builder-release" + declare BUILDER_TYPE="$1" APP="$2" IMAGE_TAG="$3" + + if [[ "$BUILDER_TYPE" != "railpack" ]]; then + return + fi + + local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG") + plugn trigger pre-release-builder "$BUILDER_TYPE" "$APP" "$IMAGE" + + TMP_WORK_DIR="$(mktemp -d "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX")" + trap "rm -rf '$TMP_WORK_DIR' >/dev/null" RETURN INT TERM EXIT + + local DOCKER_BUILD_LABEL_ARGS="--label=org.label-schema.schema-version=1.0 --label=org.label-schema.vendor=dokku --label=com.dokku.app-name=$APP --label=com.dokku.image-stage=release --label=dokku" + if ! suppress_output "$DOCKER_BIN" image build "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS -f "$PLUGIN_AVAILABLE_PATH/builder-railpack/dockerfiles/builder-release.Dockerfile" --build-arg APP_IMAGE="$IMAGE" -t "$IMAGE" "$TMP_WORK_DIR"; then + dokku_log_warn "Failure injecting docker labels on image" + return 1 + fi + + plugn trigger post-release-builder "$BUILDER_TYPE" "$APP" "$IMAGE" +} + +trigger-builder-railpack-builder-release "$@" diff --git a/plugins/builder-railpack/commands b/plugins/builder-railpack/commands new file mode 100755 index 00000000000..db753d31107 --- /dev/null +++ b/plugins/builder-railpack/commands @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/builder-railpack/help-functions" + +case "$1" in + help | builder-railpack:help) + cmd-builder-railpack-help "$@" + ;; + + *) + exit "$DOKKU_NOT_IMPLEMENTED_EXIT" + ;; + +esac diff --git a/plugins/builder-railpack/core-post-extract b/plugins/builder-railpack/core-post-extract new file mode 100755 index 00000000000..ad863c076a7 --- /dev/null +++ b/plugins/builder-railpack/core-post-extract @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/builder-railpack/internal-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-builder-railpack-core-post-extract() { + declare desc="builder-railpack post-extract plugin trigger" + declare trigger="post-extract" + declare APP="$1" SOURCECODE_WORK_DIR="$2" + local NEW_RAILPACK_JSON="$(fn-builder-railpack-computed-railpackjson-path "$APP")" + + pushd "$TMP_WORK_DIR" >/dev/null + + if [[ -z "$NEW_RAILPACK_JSON" ]]; then + return + fi + + if [[ ! -f "$NEW_RAILPACK_JSON" ]]; then + rm -f railpack.json + return + fi + + if [[ "$NEW_RAILPACK_JSON" != "railpack.json" ]]; then + mv "$NEW_RAILPACK_JSON" railpack.json + fi + popd &>/dev/null || pushd "/tmp" >/dev/null +} + +trigger-builder-railpack-core-post-extract "$@" diff --git a/plugins/builder-railpack/dockerfiles/builder-build.Dockerfile b/plugins/builder-railpack/dockerfiles/builder-build.Dockerfile new file mode 100644 index 00000000000..185c7e950ab --- /dev/null +++ b/plugins/builder-railpack/dockerfiles/builder-build.Dockerfile @@ -0,0 +1,4 @@ +ARG APP_IMAGE +FROM $APP_IMAGE + +ENTRYPOINT [] diff --git a/plugins/builder-railpack/dockerfiles/builder-release.Dockerfile b/plugins/builder-railpack/dockerfiles/builder-release.Dockerfile new file mode 100644 index 00000000000..cca256f21d5 --- /dev/null +++ b/plugins/builder-railpack/dockerfiles/builder-release.Dockerfile @@ -0,0 +1,2 @@ +ARG APP_IMAGE +FROM $APP_IMAGE diff --git a/plugins/builder-railpack/help-functions b/plugins/builder-railpack/help-functions new file mode 100755 index 00000000000..ef1811401bd --- /dev/null +++ b/plugins/builder-railpack/help-functions @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-builder-railpack-help() { + declare desc="help command" + declare CMD="$1" + local plugin_name="builder-railpack" + local plugin_description="Manage the railpack builder integration for an app" + + if [[ "$CMD" == "${plugin_name}:help" ]]; then + echo -e "Usage: dokku ${plugin_name}[:COMMAND]" + echo '' + echo "$plugin_description" + echo '' + echo 'Additional commands:' + fn-help-content | sort | column -c2 -t -s, + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + fn-help-content + else + cat <] [], Displays a builder-railpack report for one or more apps + builder-railpack:set (), Set or clear a builder-railpack property for an app +help_content +} diff --git a/plugins/builder-railpack/install b/plugins/builder-railpack/install new file mode 100755 index 00000000000..283188a3319 --- /dev/null +++ b/plugins/builder-railpack/install @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-builder-railpack-install() { + declare desc="installs the builder-railpack plugin" + declare trigger="install" + + fn-plugin-property-setup "builder-railpack" + + mkdir -p "${DOKKU_LIB_ROOT}/data/builder-railpack" + chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/builder-railpack" +} + +trigger-builder-railpack-install "$@" diff --git a/plugins/builder-railpack/internal-functions b/plugins/builder-railpack/internal-functions new file mode 100755 index 00000000000..a405b2ed072 --- /dev/null +++ b/plugins/builder-railpack/internal-functions @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-builder-railpack-report() { + declare desc="displays a builder-railpack report for one or more apps" + declare cmd="builder-railpack:report" + [[ "$1" == "$cmd" ]] && shift 1 + declare APP="$1" INFO_FLAG="$2" + + if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then + INFO_FLAG="$APP" + APP="" + fi + + if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then + INFO_FLAG="true" + fi + + if [[ -z "$APP" ]]; then + for app in $(dokku_apps); do + cmd-builder-railpack-report-single "$app" "$INFO_FLAG" | tee || true + done + else + cmd-builder-railpack-report-single "$APP" "$INFO_FLAG" + fi +} + +cmd-builder-railpack-report-single() { + declare APP="$1" INFO_FLAG="$2" + if [[ "$INFO_FLAG" == "true" ]]; then + INFO_FLAG="" + fi + verify_app_name "$APP" + local flag_map=( + "--builder-railpack-computed-railpackjson-path: $(fn-builder-railpack-computed-railpackjson-path "$APP")" + "--builder-railpack-global-railpackjson-path: $(fn-builder-railpack-global-railpackjson-path "$APP")" + "--builder-railpack-railpackjson-path: $(fn-builder-railpack-railpackjson-path "$APP")" + "--builder-railpack-computed-no-cache: $(fn-builder-railpack-computed-no-cache "$APP")" + "--builder-railpack-global-no-cache: $(fn-builder-railpack-global-no-cache "$APP")" + "--builder-railpack-no-cache: $(fn-builder-railpack-no-cache "$APP")" + ) + + if [[ -z "$INFO_FLAG" ]]; then + dokku_log_info2_quiet "${APP} builder-railpack information" + for flag in "${flag_map[@]}"; do + key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" + dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")" + done + else + local match=false + local value_exists=false + for flag in "${flag_map[@]}"; do + valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" + if [[ "$flag" == "${INFO_FLAG}:"* ]]; then + value=${flag#*: } + size="${#value}" + if [[ "$size" -ne 0 ]]; then + echo "$value" && match=true && value_exists=true + else + match=true + fi + fi + done + [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" + [[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed" + fi +} + +fn-builder-railpack-computed-railpackjson-path() { + declare APP="$1" + + file="$(fn-builder-railpack-railpackjson-path "$APP")" + if [[ "$file" == "" ]]; then + file="$(fn-builder-railpack-global-railpackjson-path "$APP")" + fi + + echo "$file" +} + +fn-builder-railpack-global-railpackjson-path() { + declare APP="$1" + + fn-plugin-property-get "builder-railpack" "--global" "railpackjson-path" "railpack.json" +} + +fn-builder-railpack-railpackjson-path() { + declare APP="$1" + + fn-plugin-property-get "builder-railpack" "$APP" "railpackjson-path" "" +} + +fn-builder-railpack-computed-no-cache() { + declare APP="$1" + + file="$(fn-builder-railpack-no-cache "$APP")" + if [[ "$file" == "" ]]; then + file="$(fn-builder-railpack-global-no-cache "$APP")" + fi + + echo "$file" +} + +fn-builder-railpack-global-no-cache() { + declare APP="$1" + + fn-plugin-property-get "builder-railpack" "--global" "no-cache" "false" +} + +fn-builder-railpack-no-cache() { + declare APP="$1" + + fn-plugin-property-get "builder-railpack" "$APP" "no-cache" "" +} diff --git a/plugins/builder-railpack/plugin.toml b/plugins/builder-railpack/plugin.toml new file mode 100644 index 00000000000..6fa72020b92 --- /dev/null +++ b/plugins/builder-railpack/plugin.toml @@ -0,0 +1,4 @@ +[plugin] +description = "dokku core builder-railpack plugin" +version = "0.36.7" +[plugin.config] diff --git a/plugins/builder-railpack/post-app-clone-setup b/plugins/builder-railpack/post-app-clone-setup new file mode 100755 index 00000000000..0ff83fc92d3 --- /dev/null +++ b/plugins/builder-railpack/post-app-clone-setup @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-builder-railpack-post-app-clone-setup() { + declare desc="sets up properties for new app" + declare trigger="post-app-clone-setup" + declare OLD_APP="$1" NEW_APP="$2" + + fn-plugin-property-clone "builder-railpack" "$OLD_APP" "$NEW_APP" +} + +trigger-builder-railpack-post-app-clone-setup "$@" diff --git a/plugins/builder-railpack/post-app-rename-setup b/plugins/builder-railpack/post-app-rename-setup new file mode 100755 index 00000000000..10effd4e57d --- /dev/null +++ b/plugins/builder-railpack/post-app-rename-setup @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-builder-railpack-post-app-rename-setup() { + declare desc="updates settings for new app" + declare trigger="post-app-rename-setup" + declare OLD_APP="$1" NEW_APP="$2" + + fn-plugin-property-clone "builder-railpack" "$OLD_APP" "$NEW_APP" + fn-plugin-property-destroy "builder-railpack" "$OLD_APP" +} + +trigger-builder-railpack-post-app-rename-setup "$@" diff --git a/plugins/builder-railpack/post-delete b/plugins/builder-railpack/post-delete new file mode 100755 index 00000000000..5879fc04652 --- /dev/null +++ b/plugins/builder-railpack/post-delete @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +trigger-builder-railpack-post-delete() { + declare desc="destroys the builder-railpack properties for a given app" + declare trigger="post-delete" + declare APP="$1" + + fn-plugin-property-destroy "builder-railpack" "$APP" + rm -rf "${DOKKU_LIB_ROOT}/data/builder-railpack/$APP" +} + +trigger-builder-railpack-post-delete "$@" diff --git a/plugins/builder-railpack/report b/plugins/builder-railpack/report new file mode 100755 index 00000000000..6fbd0d85549 --- /dev/null +++ b/plugins/builder-railpack/report @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +source "$PLUGIN_AVAILABLE_PATH/builder-railpack/internal-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-builder-railpack-report-single "$@" diff --git a/plugins/builder-railpack/subcommands/default b/plugins/builder-railpack/subcommands/default new file mode 100755 index 00000000000..cc0e6748a01 --- /dev/null +++ b/plugins/builder-railpack/subcommands/default @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +source "$PLUGIN_AVAILABLE_PATH/builder-railpack/help-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-builder-railpack-help "builder-railpack:help" diff --git a/plugins/builder-railpack/subcommands/report b/plugins/builder-railpack/subcommands/report new file mode 100755 index 00000000000..9801d5f29df --- /dev/null +++ b/plugins/builder-railpack/subcommands/report @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +source "$PLUGIN_AVAILABLE_PATH/builder-railpack/internal-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-builder-railpack-report "$@" diff --git a/plugins/builder-railpack/subcommands/set b/plugins/builder-railpack/subcommands/set new file mode 100755 index 00000000000..6001f0c6f8a --- /dev/null +++ b/plugins/builder-railpack/subcommands/set @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-builder-railpack-set() { + declare desc="set or clear a builder-railpack property for an app" + declare cmd="builder-railpack:set" + [[ "$1" == "$cmd" ]] && shift 1 + declare APP="$1" KEY="$2" VALUE="$3" + local VALID_KEYS=("railpackjson-path" "no-cache") + [[ "$APP" == "--global" ]] || verify_app_name "$APP" + + [[ -z "$KEY" ]] && dokku_log_fail "No key specified" + + if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then + dokku_log_fail "Invalid key specified, valid keys include: railpackjson-path, no-cache" + fi + + if [[ -n "$VALUE" ]]; then + dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" + fn-plugin-property-write "builder-railpack" "$APP" "$KEY" "$VALUE" + else + dokku_log_info2_quiet "Unsetting ${KEY}" + fn-plugin-property-delete "builder-railpack" "$APP" "$KEY" + fi +} + +cmd-builder-railpack-set "$@" diff --git a/plugins/common/exec.go b/plugins/common/exec.go index c5790ba6e37..367449b8bff 100644 --- a/plugins/common/exec.go +++ b/plugins/common/exec.go @@ -29,6 +29,9 @@ type ExecCommandInput struct { // Env is the environment variables to pass to the command Env map[string]string + // PrintCommand prints the command before executing + PrintCommand bool + // Stdin is the stdin of the command Stdin io.Reader @@ -141,7 +144,7 @@ func CallExecCommandWithContext(ctx context.Context, input ExecCommandInput) (Ex cmd.Cwd = input.WorkingDirectory } - if os.Getenv("DOKKU_TRACE") == "1" { + if input.PrintCommand || os.Getenv("DOKKU_TRACE") == "1" { argsSt := "" if len(cmd.Args) > 0 { argsSt = strings.Join(cmd.Args, " ") diff --git a/plugins/common/plugn.go b/plugins/common/plugn.go index 8ce905ac511..4acf58a21b3 100644 --- a/plugins/common/plugn.go +++ b/plugins/common/plugn.go @@ -19,6 +19,9 @@ type PlugnTriggerInput struct { // Env is the environment variables to pass to the trigger Env map[string]string + // PrintCommand prints the command before executing + PrintCommand bool + // Stdin is the stdin of the command Stdin io.Reader @@ -55,7 +58,7 @@ func CallPlugnTriggerWithContext(ctx context.Context, input PlugnTriggerInput) ( StreamStderr: input.StreamStderr, }) - if os.Getenv("DOKKU_TRACE") == "1" { + if input.PrintCommand || os.Getenv("DOKKU_TRACE") == "1" { for _, line := range strings.Split(result.Stderr, "\n") { LogDebug(fmt.Sprintf("plugn trigger %s stderr: %s", input.Trigger, line)) } diff --git a/plugins/common/sftp.go b/plugins/common/sftp.go index 1d8ade339d4..2758ff4e34a 100644 --- a/plugins/common/sftp.go +++ b/plugins/common/sftp.go @@ -219,7 +219,7 @@ func (task SftpCopyTask) Execute(ctx context.Context) (SftpCopyResult, error) { }, ctx.Err() } - if task.PrintCommand { + if task.PrintCommand || os.Getenv("DOKKU_TRACE") == "1" { LogDebug(fmt.Sprintf("ssh %s@%s cp %s %v", task.Username, task.Hostname, task.SourcePath, task.DestinationPath)) } diff --git a/plugins/common/ssh.go b/plugins/common/ssh.go index c23f63bc92f..f823c75c3f0 100644 --- a/plugins/common/ssh.go +++ b/plugins/common/ssh.go @@ -203,6 +203,7 @@ type SshTask struct { // PrintCommand prints the command before executing PrintCommand bool + // StreamStdio prints stdout and stderr directly to os.Stdout/err as // the command runs. StreamStdio bool @@ -334,7 +335,7 @@ func (task SshTask) Execute(ctx context.Context) (SshResult, error) { command = "sudo" } - if task.PrintCommand { + if task.PrintCommand || os.Getenv("DOKKU_TRACE") == "1" { LogDebug(fmt.Sprintf("ssh %s@%s %s %v", task.Username, task.Hostname, command, commandArgs)) } diff --git a/plugins/docker-options/docker-args-deploy b/plugins/docker-options/docker-args-deploy index fc768a0cf28..35ea4bc97dd 100755 --- a/plugins/docker-options/docker-args-deploy +++ b/plugins/docker-options/docker-args-deploy @@ -47,7 +47,7 @@ trigger-docker-options-docker-args() { *) case "$IMAGE_SOURCE_TYPE" in - dockerfile | nixpacks) + dockerfile | nixpacks | railpack) case "$line" in --link* | -v* | --volume*) continue diff --git a/tests/unit/builder-railpack.bats b/tests/unit/builder-railpack.bats new file mode 100644 index 00000000000..e0dcc5f6cdd --- /dev/null +++ b/tests/unit/builder-railpack.bats @@ -0,0 +1,62 @@ +#!/usr/bin/env bats + +load test_helper + +setup_file() { + global_setup + install_railpack + docker run --rm --privileged -d --name buildkit moby/buildkit:latest + touch /etc/default/dokku + sudo tee -a /etc/default/dokku <<<"export BUILDKIT_HOST='docker-container://buildkit'" +} + +teardown_file() { + docker container stop buildkit || true + docker container rm buildkit || true +} + +setup() { + create_app +} + +teardown() { + destroy_app + global_teardown +} + +@test "(builder-railpack:set)" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected railpack" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP inject_requirements_txt + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'Successfully built image in' + assert_output_contains "SECRET_KEY: fjdkslafjdk" + + run /bin/bash -c "dokku builder-railpack:set $TEST_APP railpackjson-path nonexistent.json" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'Successfully built image in' +} + +inject_requirements_txt() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + echo "flask" >>"$APP_REPO_DIR/requirements.txt" +} diff --git a/tests/unit/report.bats b/tests/unit/report.bats index a5f7271b605..719af8c800b 100644 --- a/tests/unit/report.bats +++ b/tests/unit/report.bats @@ -103,6 +103,7 @@ teardown() { builder-herokuish builder-lambda builder-nixpacks + builder-railpack builder-pack buildpacks caddy diff --git a/tests/unit/scheduler-k3s-1.bats b/tests/unit/scheduler-k3s-1.bats index f9fbcd45578..b89be6d0ea9 100644 --- a/tests/unit/scheduler-k3s-1.bats +++ b/tests/unit/scheduler-k3s-1.bats @@ -11,7 +11,7 @@ setup() { export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" } -teardown_() { +teardown() { global_teardown dokku nginx:start uninstall_k3s || true diff --git a/tests/unit/scheduler-k3s-2.bats b/tests/unit/scheduler-k3s-2.bats index b1c47e8816e..978bfa53140 100644 --- a/tests/unit/scheduler-k3s-2.bats +++ b/tests/unit/scheduler-k3s-2.bats @@ -11,7 +11,7 @@ setup() { export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" } -teardown_() { +teardown() { global_teardown dokku nginx:start uninstall_k3s || true diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index 34ce53bcbc7..c53880f2497 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -660,6 +660,12 @@ install_nixpacks() { fi } +install_railpack() { + if ! command -v "railpack" &>/dev/null; then + curl -sSL https://railpack.com/install.sh | FORCE=1 bash + fi +} + install_k3s() { run /bin/bash -c "dokku proxy:set --global k3s" echo "output: $output"