From 451abcf8f30ac990691b7b70682b05a305c9240a Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 1 Dec 2020 14:49:39 -0500 Subject: [PATCH] feat: add initial support for cloud native buildpacks This change adds a new builder - builder-cnb - via the 'DOKKU_CNB_EXPERIMENTAL' environment variable. While support for customizing pack builds is not currently implemented and there may be other missing functionality, this initial implementation allows folks to start playing with CNB. Future releases of Dokku will provide tighter CNB integration and eventually switch to CNB for default building, as well as allow users to better select builders needed for their applications. All new code for CNB support should be considered experimental and subject to change as the support evolves over time. --- docs/deployment/application-deployment.md | 4 +- .../methods/cloud-native-buildpacks.md | 35 ++++++++ docs/deployment/methods/git.md | 6 +- ...{buildpacks.md => herokuish-buildpacks.md} | 2 +- docs/development/plugin-creation.md | 4 +- docs/development/plugin-triggers.md | 80 +++++++++++++++++++ docs/template.html | 3 +- docs/viewdocs.json | 3 +- plugins/20_events/post-build-cnb | 1 + plugins/20_events/post-release-cnb | 1 + plugins/20_events/pre-build-cnb | 1 + plugins/20_events/pre-release-cnb | 1 + plugins/builder-cnb/builder-build | 38 +++++++++ plugins/builder-cnb/builder-release | 22 +++++ plugins/builder-cnb/plugin.toml | 4 + plugins/builder-dockerfile/builder-release | 1 - plugins/common/functions | 48 +++++++++-- .../scheduler-docker-local/scheduler-enter | 3 +- 18 files changed, 240 insertions(+), 17 deletions(-) create mode 100644 docs/deployment/methods/cloud-native-buildpacks.md rename docs/deployment/methods/{buildpacks.md => herokuish-buildpacks.md} (99%) create mode 120000 plugins/20_events/post-build-cnb create mode 120000 plugins/20_events/post-release-cnb create mode 120000 plugins/20_events/pre-build-cnb create mode 120000 plugins/20_events/pre-release-cnb create mode 100755 plugins/builder-cnb/builder-build create mode 100755 plugins/builder-cnb/builder-release create mode 100644 plugins/builder-cnb/plugin.toml diff --git a/docs/deployment/application-deployment.md b/docs/deployment/application-deployment.md index bc08f9c9c..ff8275446 100644 --- a/docs/deployment/application-deployment.md +++ b/docs/deployment/application-deployment.md @@ -102,7 +102,7 @@ Once the deploy is complete, the application's web URL will be generated as abov Dokku supports deploying applications in a few ways: -- [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks) via [Herokuish](https://github.com/gliderlabs/herokuish#buildpacks): See the [buildpacks documentation](/docs/deployment/methods/buildpacks.md) to learn about the different ways to specify a buildpack. +- [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks) via [Herokuish](https://github.com/gliderlabs/herokuish#buildpacks): See the [herokuish buildpacks documentation](/docs/deployment/methods/herokuish-buildpacks.md) to learn about the different ways to specify a buildpack. - [Dockerfile](https://docs.docker.com/reference/builder/): See the [dockerfile documentation](/docs/deployment/methods/dockerfiles.md) to learn about the different ways to configure Dockerfile-based deploys. - [Docker Image](https://docs.docker.com/get-started/overview/#docker-objects): See the [docker image documentation](/docs/deployment/methods/images.md) to learn about how to deploy a Docker Image. @@ -211,7 +211,7 @@ See the [image tagging documentation](/docs/deployment/methods/images.md) for mo ## Specifying a custom buildpack -See the [buildpack documentation](/docs/deployment/methods/buildpacks.md) for more information on how to specify a set of custom buildpacks for your application. +See the [herokuish buildpack documentation](/docs/deployment/methods/herokuish-buildpacks.md) for more information on how to specify a set of custom buildpacks for your application. ## Removing a deployed app diff --git a/docs/deployment/methods/cloud-native-buildpacks.md b/docs/deployment/methods/cloud-native-buildpacks.md new file mode 100644 index 000000000..0237626d8 --- /dev/null +++ b/docs/deployment/methods/cloud-native-buildpacks.md @@ -0,0 +1,35 @@ +# Cloud Native Buildpacks (Experimental) + +> New as of 0.22.0 + +Cloud Native Buildpacks are an evolution over the Buildpacks technology provided by the Herokuish builder. See the [herokuish buildpacks documentation](/docs/deployment/methods/herokuish.md) for more information on how to clear buildpack build cache for an application. + +> Warning: This functionality uses the `pack` cli from the [Cloud Native Buildpacks](https://buildpacks.io) project to build apps. As the integration is experimental in Dokku, it is likely to change over time. + +## Usage + +To use this builder instead of either `Dockerfile` or `herokuish`, you must set the `DOKKU_CNB_EXPERIMENTAL` environment variable for your app to `1`. + +```shell +dokku config:set --no-restart node-js-app DOKKU_CNB_EXPERIMENTAL=1 +``` + +The `pack` cli tool is not included by default with Dokku or as a dependency. It must also be installed as shown on [this page](https://buildpacks.io/docs/tools/pack/). + +Builds will proceed with the `pack` cli for the app from then on. + +## Caveats + +As this functionality is highly experimental, there are a number of caveats. Please note that not all issuesare listed below. + +- The `heroku/buildpacks:latest` CNB builder is currently enforced. Specifying specific builders is not currently possible. + - A future release will allow modifying the chosen builder. +- Specifying specific buildpacks is not currently possible. + - A future release will add support for the `buildpacks` plugin. +- There is currently no way to specify extra arguments for `pack` cli invocations. + - A future release will add support for injecting extra arguments during the build process. +- The default process type is `web`. +- Build cache is stored in Docker volumes instead of on disk. As such, `repo:purge-cache` currently has no effect. + - A future version will add integration with the `repo` plugin. +- `pack` is not currently included with Dokku, nor is it added as a package dependency. + - A future version will include it as a package dependency. diff --git a/docs/deployment/methods/git.md b/docs/deployment/methods/git.md index a13af5c15..4bc071ef6 100644 --- a/docs/deployment/methods/git.md +++ b/docs/deployment/methods/git.md @@ -8,7 +8,11 @@ git:report [] [] # Displays a git report for one or more git:set () # Set or clear a git property for an app ``` -Git-based deployment has been the traditional method of deploying applications in Dokku. As of v0.12.0, Dokku introduces a few ways to customize the experience of deploying via `git push`. A Git-based deployment currently supports building applications via both [Buildpack](/docs/deployment/methods/buildpacks.md) and [Dockerfile](/docs/deployment/methods/dockerfiles.md). +Git-based deployment has been the traditional method of deploying applications in Dokku. As of v0.12.0, Dokku introduces a few ways to customize the experience of deploying via `git push`. A Git-based deployment currently supports building applications via: + +- [Cloud Native Buildpacks](/docs/deployment/methods/cloud-native-buildpacks.md) +- [Herokuish Buildpack](/docs/deployment/methods/herokuish-buildpacks.md) +- [Dockerfiles](/docs/deployment/methods/dockerfiles.md) ## Usage diff --git a/docs/deployment/methods/buildpacks.md b/docs/deployment/methods/herokuish-buildpacks.md similarity index 99% rename from docs/deployment/methods/buildpacks.md rename to docs/deployment/methods/herokuish-buildpacks.md index 5a64f58e9..e72d3236c 100644 --- a/docs/deployment/methods/buildpacks.md +++ b/docs/deployment/methods/herokuish-buildpacks.md @@ -1,4 +1,4 @@ -# Buildpack Deployment +# Herokuish Buildpack Deployment > Subcommands new as of 0.15.0 diff --git a/docs/development/plugin-creation.md b/docs/development/plugin-creation.md index 3b703de1d..e2a26e999 100644 --- a/docs/development/plugin-creation.md +++ b/docs/development/plugin-creation.md @@ -97,12 +97,12 @@ If a plugin depends on a specific command-line tool, check whether that tool exi ```shell # `command -v` example if ! command -v "nginx" &>/dev/null; then - log-fail "Missing nginx, install it" + dokku_log_fail "Missing nginx, install it" fi # `which` example if ! which nginx >/dev/null 2>&1; then - log-fail "Missing nginx, install it" + dokku_log_fail "Missing nginx, install it" fi ``` diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index fab7d8406..6690ad8d8 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -1015,6 +1015,24 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `post-build-cnb` + +> Warning: The cnb plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to run commands after the build image is create for a given app. Only applies to apps using cnb. +- Invoked by: `internal function dokku_build() (build phase)` +- Arguments: `$APP` `$SOURCECODE_WORK_DIR` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `post-build-dockerfile` - Description: Allows you to run commands after the build image is create for a given app. Only applies to apps using a dockerfile. @@ -1231,6 +1249,29 @@ verify_app_name "$APP" # TODO ``` +### `post-release-cnb` + +> Warning: The cnb plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +> Warning: Image mutation in this trigger may result in an invalid run state, and is heavily discouraged. + +- Description: Allows you to run commands after environment variables are set for the release step of the deploy. Only applies to apps using cnb. +- Invoked by: `internal function dokku_release() (release phase)` +- Arguments: `$APP $IMAGE_TAG` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; IMAGE_TAG="$2"; IMAGE=$(get_app_image_name $APP $IMAGE_TAG) +verify_app_name "$APP" + +# TODO +``` + ### `post-release-dockerfile` > Warning: Image mutation in this trigger may result in an invalid run state, and is heavily discouraged. @@ -1284,6 +1325,24 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `pre-build-cnb` + +> Warning: The cnb plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to run commands before the build image is created for a given app. For instance, this can be useful to add env vars to your container. Only applies to apps using cnb. +- Invoked by: `internal function dokku_build() (build phase)` +- Arguments: `$APP` `$SOURCECODE_WORK_DIR` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `pre-build-dockerfile` - Description: Allows you to run commands before the build image is created for a given app. For instance, this can be useful to add env vars to your container. Only applies to apps using a dockerfile. @@ -1425,6 +1484,27 @@ DOCKER_COMMIT_LABEL_ARGS=("--change" "LABEL org.label-schema.schema-version=1.0" docker commit "${DOCKER_COMMIT_LABEL_ARGS[@]}" $CID $IMAGE >/dev/null ``` + +### `pre-release-cnb` + +> Warning: The cnb plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to run commands before environment variables are set for the release step of the deploy. Only applies to apps using cnb. +- Invoked by: `internal function dokku_release() (release phase)` +- Arguments: `$APP $IMAGE_TAG` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; IMAGE_TAG="$2"; + +# TODO +``` + ### `pre-release-dockerfile` - Description: Allows you to run commands before environment variables are set for the release step of the deploy. Only applies to apps using a dockerfile. diff --git a/docs/template.html b/docs/template.html index 97b15f9fa..ea9373807 100644 --- a/docs/template.html +++ b/docs/template.html @@ -128,7 +128,8 @@ Deployment Methods - Buildpack Deployment + Cloud Native Buildpacks + Herokuish Buildpacks Dockerfile Deployment Docker Image Deployment Git Deployment diff --git a/docs/viewdocs.json b/docs/viewdocs.json index e37e59f74..15bc26ce1 100644 --- a/docs/viewdocs.json +++ b/docs/viewdocs.json @@ -10,7 +10,8 @@ "process-management": "deployment/process-management/", "remote-commands": "deployment/remote-commands/", - "deployment/buildpacks": "deployment/methods/buildpacks/", + "deployment/buildpacks": "deployment/methods/herokuish-buildpacks/", + "deployment/methods/buildpacks": "deployment/methods/herokuish-buildpacks/", "deployment/dockerfiles": "deployment/methods/dockerfiles/", "deployment/images": "deployment/methods/images/", "configuration-management": "configuration/environment-variables/", diff --git a/plugins/20_events/post-build-cnb b/plugins/20_events/post-build-cnb new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/post-build-cnb @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/post-release-cnb b/plugins/20_events/post-release-cnb new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/post-release-cnb @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/pre-build-cnb b/plugins/20_events/pre-build-cnb new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/pre-build-cnb @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/pre-release-cnb b/plugins/20_events/pre-release-cnb new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/pre-release-cnb @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/builder-cnb/builder-build b/plugins/builder-cnb/builder-build new file mode 100755 index 000000000..83f1e89a4 --- /dev/null +++ b/plugins/builder-cnb/builder-build @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" + +trigger-builder-cnb-builder-build() { + declare desc="builder-cnb builder-build plugin trigger" + declare trigger="builder-build" + declare BUILDER_TYPE="$1" APP="$2" SOURCECODE_WORK_DIR="$3" + + if [[ "$BUILDER_TYPE" != "cnb" ]]; then + return + fi + + if ! command -v "pack" &>/dev/null; then + dokku_log_fail "Missing pack, install it" + fi + + local IMAGE=$(get_app_image_name "$APP") + + pushd "$SOURCECODE_WORK_DIR" &>/dev/null + + local TMP_FILE=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") + trap "rm -rf '$TMP_FILE' >/dev/null" RETURN INT TERM + + config_export app "$APP" --format envfile --merged > "$TMP_FILE" + + plugn trigger pre-build-cnb "$APP" "$SOURCECODE_WORK_DIR" + + pack build "$IMAGE" --builder heroku/buildpacks --path "$SOURCECODE_WORK_DIR" --default-process web + + echo "FROM $IMAGE" | suppress_output "$DOCKER_BIN" image build --label=com.dokku.image-stage=build --label=org.label-schema.schema-version=1.0 --label=org.label-schema.vendor=dokku --label=com.dokku.app-name=$APP --label=dokku -t "$IMAGE" - + + plugn trigger post-build-cnb "$APP" "$SOURCECODE_WORK_DIR" +} + +trigger-builder-cnb-builder-build "$@" diff --git a/plugins/builder-cnb/builder-release b/plugins/builder-cnb/builder-release new file mode 100755 index 000000000..1591417fd --- /dev/null +++ b/plugins/builder-cnb/builder-release @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +trigger-builder-cnb-builder-release() { + declare desc="builder-cnb builder-release plugin trigger" + declare trigger="builder-release" + declare BUILDER_TYPE="$1" APP="$2" IMAGE_TAG="$3" + + if [[ "$BUILDER_TYPE" != "cnb" ]]; then + return + fi + + plugn trigger pre-release-cnb "$APP" "$IMAGE_TAG" + + local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG") + echo "FROM $IMAGE" | suppress_output "$DOCKER_BIN" image build --label=com.dokku.image-stage=release --label=com.dokku.app-name=$APP --label=org.label-schema.schema-version=1.0 --label=org.label-schema.vendor=dokku --label=dokku -t "$IMAGE" - + plugn trigger post-release-cnb "$APP" "$IMAGE_TAG" +} + +trigger-builder-cnb-builder-release "$@" diff --git a/plugins/builder-cnb/plugin.toml b/plugins/builder-cnb/plugin.toml new file mode 100644 index 000000000..7c09dd2de --- /dev/null +++ b/plugins/builder-cnb/plugin.toml @@ -0,0 +1,4 @@ +[plugin] +description = "dokku core builder-cnb plugin" +version = "0.21.4" +[plugin.config] diff --git a/plugins/builder-dockerfile/builder-release b/plugins/builder-dockerfile/builder-release index 6f171ad11..5f078cf8b 100755 --- a/plugins/builder-dockerfile/builder-release +++ b/plugins/builder-dockerfile/builder-release @@ -12,7 +12,6 @@ trigger-builder-dockerfile-builder-release() { return fi - # buildstep plugins don't necessarily make sense for dockerfiles. call the new breed!!! plugn trigger pre-release-dockerfile "$APP" "$IMAGE_TAG" local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG") diff --git a/plugins/common/functions b/plugins/common/functions index 8b0b06905..fbf9eaf5d 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -293,20 +293,41 @@ get_running_image_tag() { echo "$RUNNING_IMAGE_TAG" } +is_image_cnb_based() { + declare desc="returns true if app image is based on cnb" + declare IMAGE="$1" + + if [[ -z "$IMAGE" ]]; then + return 1 + fi + + CNB_STACK_ID="$("$DOCKER_BIN" image inspect --format '{{ index .Config.Labels "io.buildpacks.stack.id" }}' "$IMAGE")" + if [[ -n "$CNB_STACK_ID" ]]; then + return 0 + fi + + return 1 +} + is_image_herokuish_based() { - declare desc="returns true if app image is based on herokuish" + declare desc="returns true if app image is based on herokuish or a buildpack-like env" declare IMAGE="$1" APP="$2" local DOKKU_APP_USER - if [[ -n "$APP" ]]; then - DOKKU_APP_USER=$(config_get "$APP" DOKKU_APP_USER || true) - fi - DOKKU_APP_USER=${DOKKU_APP_USER:="herokuishuser"} local USER_VALUE if [[ -z "$IMAGE" ]]; then return 1 fi + if is_image_cnb_based "$IMAGE"; then + return 0 + fi + + if [[ -n "$APP" ]]; then + DOKKU_APP_USER=$(config_get "$APP" DOKKU_APP_USER || true) + fi + DOKKU_APP_USER=${DOKKU_APP_USER:="herokuishuser"} + # due to how the build process works, all herokuish images have the Environment variable USER=$DOKKU_APP_USER USER_VALUE="$("$DOCKER_BIN" image inspect --format '{{range .Config.Env}}{{if eq . "USER='"$DOKKU_APP_USER"'"}}{{println .}}{{end}}{{end}}' "$IMAGE")" [[ "$USER_VALUE" == "" ]] && return 1 @@ -566,6 +587,11 @@ dokku_release() { declare desc="release phase" declare APP="$1" IMAGE_SOURCE_TYPE="$2" IMAGE_TAG="$3" + local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG") + if is_image_cnb_based "$IMAGE"; then + IMAGE_SOURCE_TYPE="cnb" + fi + plugn trigger builder-release "$IMAGE_SOURCE_TYPE" "$APP" "$IMAGE_TAG" } @@ -630,9 +656,17 @@ dokku_receive() { local IMAGE=$(get_app_image_name "$APP") local IMAGE_SOURCE_TYPE="$2" local TMP_WORK_DIR="$3" - local DOKKU_SKIP_CLEANUP="$(config_get "$APP" DOKKU_SKIP_CLEANUP || true)" + docker_cleanup "$APP" - dokku_log_info1 "Building $APP from $IMAGE_SOURCE_TYPE..." + + local DOKKU_CNB_EXPERIMENTAL="$(config_get "$APP" DOKKU_CNB_EXPERIMENTAL || true)" + if [[ "$DOKKU_CNB_EXPERIMENTAL" == "1" ]]; then + IMAGE_SOURCE_TYPE="cnb" + dokku_log_info1 "Building $APP from $IMAGE_SOURCE_TYPE (experimental)..." + else + dokku_log_info1 "Building $APP from $IMAGE_SOURCE_TYPE..." + fi + DOKKU_QUIET_OUTPUT=1 config_set --no-restart "$APP" DOKKU_APP_TYPE="$IMAGE_SOURCE_TYPE" dokku_build "$APP" "$IMAGE_SOURCE_TYPE" "$TMP_WORK_DIR" plugn trigger release-and-deploy "$APP" diff --git a/plugins/scheduler-docker-local/scheduler-enter b/plugins/scheduler-docker-local/scheduler-enter index 1d4c8b422..a41ba178a 100755 --- a/plugins/scheduler-docker-local/scheduler-enter +++ b/plugins/scheduler-docker-local/scheduler-enter @@ -72,7 +72,8 @@ trigger-scheduler-docker-local-scheduler-enter() { local EXEC_CMD="" has_tty && local DOKKU_RUN_OPTS+=" -i -t" local IMAGE="$("$DOCKER_BIN" container inspect --format '{{ .Config.Image }}' "$CONTAINER_ID")" - is_image_herokuish_based "$IMAGE" "$APP" && local EXEC_CMD="/exec" + is_image_herokuish_based "$IMAGE" "$APP" && EXEC_CMD="/exec" + is_image_cnb_based "$IMAGE" && EXEC_CMD="" && DOKKU_RUN_OPTS+=" -w /app" # shellcheck disable=SC2086 "$DOCKER_BIN" container exec $DOKKU_RUN_OPTS "$CONTAINER_ID" $EXEC_CMD "${@:-$DOKKU_APP_SHELL}" }