Merge pull request #8086 from dokku/0.37-release

Release 0.37.0
This commit is contained in:
Jose Diaz-Gonzalez
2025-11-20 10:39:54 -05:00
committed by GitHub
179 changed files with 4743 additions and 646 deletions

View File

@@ -33,6 +33,11 @@ RUN mkdir -p /etc/apt/keyrings \
&& NIXPACKS_BIN_DIR=/usr/bin BIN_DIR=/usr/bin /tmp/nixpacks.bash \ && NIXPACKS_BIN_DIR=/usr/bin BIN_DIR=/usr/bin /tmp/nixpacks.bash \
&& test -x /usr/bin/nixpacks \ && test -x /usr/bin/nixpacks \
&& rm -rf /tmp/nixpacks.bash \ && 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/hostname string $DOKKU_HOSTNAME" | debconf-set-selections \
&& echo "dokku dokku/skip_key_file boolean $DOKKU_SKIP_KEY_FILE" | 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 \ && echo "dokku dokku/vhost_enable boolean $DOKKU_VHOST_ENABLE" | debconf-set-selections \

View File

@@ -86,10 +86,10 @@
}, },
{ {
"name": "herokuish", "name": "herokuish",
"version": "0.11.3", "version": "0.11.5",
"urls": { "urls": {
"amd64": "https://github.com/gliderlabs/herokuish/releases/download/v0.11.3/herokuish_0.11.3_linux_x86_64.tgz", "amd64": "https://github.com/gliderlabs/herokuish/releases/download/v0.11.5/herokuish_0.11.5_linux_x86_64.tgz",
"arm64": "https://github.com/gliderlabs/herokuish/releases/download/v0.11.3/herokuish_0.11.3_linux_x86_64.tgz" "arm64": "https://github.com/gliderlabs/herokuish/releases/download/v0.11.5/herokuish_0.11.5_linux_x86_64.tgz"
} }
} }
] ]

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -eo pipefail set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x [[ $DOKKU_TRACE ]] && set -x
export DOKKU_PORT=${DOKKU_PORT:=22} export DOKKU_PORT=${DOKKU_PORT:=}
export DOKKU_HOST=${DOKKU_HOST:=} export DOKKU_HOST=${DOKKU_HOST:=}
export DOKKU_APP_PATH=${DOKKU_APP_PATH:=}
fn-random-number() { fn-random-number() {
[[ -n "$1" ]] && RANGE="$1" [[ -n "$1" ]] && RANGE="$1"
@@ -61,12 +62,42 @@ fn-dokku-host() {
echo "$DOKKU_HOST" echo "$DOKKU_HOST"
} }
fn-dokku-app() {
declare DOKKU_GIT_REMOTE="$1" DOKKU_REMOTE_HOST="$2"
remote_output="$(git remote -v 2>/dev/null | grep -Ei "^${DOKKU_GIT_REMOTE}\s" | head -n 1 | awk '{print $2}')"
# check if there is a scheme
if echo "$remote_output" | grep -qEi "ssh://"; then
echo "$remote_output" | grep -Ei "ssh://dokku@$DOKKU_REMOTE_HOST" | cut -f2 -d'@' | cut -f2 -d'/' 2>/dev/null
else
echo "$remote_output" | grep -Ei "dokku@$DOKKU_REMOTE_HOST" | cut -f2 -d'@' | cut -f2 -d':' 2>/dev/null
fi
}
fn-dokku-port() {
declare DOKKU_GIT_REMOTE="$1" DOKKU_REMOTE_HOST="$2"
remote_output="$(git remote -v 2>/dev/null | grep -Ei "^${DOKKU_GIT_REMOTE}\s" | head -n 1 | awk '{print $2}')"
if echo "$remote_output" | grep -qEi "ssh://"; then
local output="$(echo "$remote_output" | grep -Ei "ssh://dokku@$DOKKU_REMOTE_HOST" | cut -f2 -d'@' | cut -f1 -d'/' 2>/dev/null)"
# check if output has a : and if so, get the port
if echo "$output" | grep -qEi ":"; then
echo "$output" | cut -f2 -d':' 2>/dev/null
else
echo "22"
fi
else
echo "22"
fi
}
fn-get-remote() { fn-get-remote() {
git config dokku.remote 2>/dev/null || echo "dokku" git config dokku.remote 2>/dev/null || echo "dokku"
} }
main() { main() {
declare CMD="$1" APP_ARG="$2" declare CMD="$1" APP_ARG="$2"
local APP="" DOKKU_GIT_REMOTE="$(fn-get-remote)" DOKKU_REMOTE_HOST="" local APP="" DOKKU_GIT_REMOTE="$(fn-get-remote)" DOKKU_REMOTE_HOST=""
local cmd_set=false next_index=1 skip=false args=("$@") local cmd_set=false next_index=1 skip=false args=("$@")
@@ -84,6 +115,10 @@ main() {
APP=${args[$next_index]} APP=${args[$next_index]}
skip=true skip=true
shift 2 shift 2
elif [[ "$arg" == "--app-path" ]]; then
DOKKU_APP_PATH=${args[$next_index]}
skip=true
shift 2
elif [[ "$arg" == "--remote" ]]; then elif [[ "$arg" == "--remote" ]]; then
DOKKU_GIT_REMOTE=${args[$next_index]} DOKKU_GIT_REMOTE=${args[$next_index]}
skip=true skip=true
@@ -103,6 +138,11 @@ main() {
next_index=$((next_index + 1)) next_index=$((next_index + 1))
done done
if [[ -n "$DOKKU_APP_PATH" ]]; then
pushd "$DOKKU_APP_PATH" &>/dev/null || exit 1
trap 'popd &>/dev/null || true' RETURN INT TERM
fi
DOKKU_REMOTE_HOST="$(fn-dokku-host "$DOKKU_GIT_REMOTE" "$DOKKU_HOST")" DOKKU_REMOTE_HOST="$(fn-dokku-host "$DOKKU_GIT_REMOTE" "$DOKKU_HOST")"
if [[ -z "$DOKKU_REMOTE_HOST" ]] && [[ "$CMD" != remote ]] && [[ "$CMD" != remote:* ]]; then if [[ -z "$DOKKU_REMOTE_HOST" ]] && [[ "$CMD" != remote ]] && [[ "$CMD" != remote:* ]]; then
fn-client-help-msg fn-client-help-msg
@@ -111,14 +151,28 @@ main() {
if [[ -z "$APP" ]]; then if [[ -z "$APP" ]]; then
if [[ -d .git ]] || git rev-parse --git-dir &>/dev/null; then if [[ -d .git ]] || git rev-parse --git-dir &>/dev/null; then
set +e set +e
APP=$(git remote -v 2>/dev/null | grep -Ei "^${DOKKU_GIT_REMOTE}\s" | grep -Ei "dokku@$DOKKU_REMOTE_HOST" | head -n 1 | cut -f2 -d'@' | cut -f1 -d' ' | cut -f2 -d':' 2>/dev/null) APP="$(fn-dokku-app "$DOKKU_GIT_REMOTE" "$DOKKU_REMOTE_HOST")"
set -e set -e
else else
echo " ! This is not a git repository" 1>&2 echo " ! This is not a git repository" 1>&2
fi fi
fi fi
if [[ -z "$DOKKU_PORT" ]]; then
DOKKU_PORT="$(fn-dokku-port "$DOKKU_GIT_REMOTE" "$DOKKU_REMOTE_HOST")"
fi
case "$CMD" in case "$CMD" in
remote:show)
echo "dokku-app: $APP"
echo "dokku-git-remote: $DOKKU_GIT_REMOTE"
echo "dokku-host: $DOKKU_HOST"
echo "dokku-port: $DOKKU_PORT"
echo "dokku-remote-host: $DOKKU_REMOTE_HOST"
echo "dokku-ssh-user: dokku"
echo "dokku-constructed-remote: ssh://dokku@$DOKKU_REMOTE_HOST:$DOKKU_PORT/$APP"
exit 0
;;
apps:create) apps:create)
if [[ -z "$APP" ]] && [[ -z "$APP_ARG" ]]; then if [[ -z "$APP" ]] && [[ -z "$APP_ARG" ]]; then
APP=$(fn-random-name) APP=$(fn-random-name)

3
debian/postinst vendored
View File

@@ -128,9 +128,6 @@ case "$1" in
configure) configure)
mandb mandb
[ ! -x /usr/bin/docker.io ] || ln -sf /usr/bin/docker.io /usr/local/bin/docker [ ! -x /usr/bin/docker.io ] || ln -sf /usr/bin/docker.io /usr/local/bin/docker
if [[ -f /sbin/modprobe ]]; then
modprobe aufs || echo "WARNING: Restart server to finish installing dokku!"
fi
setup-user setup-user
setup-storage setup-storage

View File

@@ -86,7 +86,7 @@ main() {
fi fi
# run core install triggers to force create any folders that may be missing # run core install triggers to force create any folders that may be missing
dokku plugin:install --core dokku plugin:install
if [[ -n "$DOKKU_HOSTNAME" ]]; then if [[ -n "$DOKKU_HOSTNAME" ]]; then
echo "dokku dokku/hostname string $DOKKU_HOSTNAME" | debconf-set-selections echo "dokku dokku/hostname string $DOKKU_HOSTNAME" | debconf-set-selections

View File

@@ -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

View File

@@ -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 "$@"

View File

@@ -6,7 +6,7 @@
``` ```
plugin:disable <name> # Disable an installed plugin (third-party only) plugin:disable <name> # Disable an installed plugin (third-party only)
plugin:enable <name> # Enable a previously disabled plugin plugin:enable <name> # Enable a previously disabled plugin
plugin:install [--core|git-url] [--committish branch|commit|tag] [--name custom-plugin-name] # Optionally download git-url (and pin to the specified branch/commit/tag) & run install trigger for active plugins (or only core ones) plugin:install [--core|git-url] [--committish branch|commit|tag] [--name custom-plugin-name] [--skip-install-trigger] # Optionally download git-url (and pin to the specified branch/commit/tag) & run install trigger for active plugins (or only core ones)
plugin:installed <name> # Checks if a plugin is installed plugin:installed <name> # Checks if a plugin is installed
plugin:install-dependencies [--core] # Run install-dependencies trigger for active plugins (or only core ones) plugin:install-dependencies [--core] # Run install-dependencies trigger for active plugins (or only core ones)
plugin:list # Print active plugins plugin:list # Print active plugins
@@ -128,6 +128,12 @@ The `--core` flag may also be indicated as the sole argument, though it is only
dokku plugin:install --core dokku plugin:install --core
``` ```
If installing plugins in a Dockerfile, you will want to skip the `install` trigger. This will be run on container boot.
```shell
dokku plugin:install https://github.com/dokku/smoke-test-plugin.git --name smoke-test-plugin --skip-install-trigger
```
Finally, all flags may be omitted to trigger the `install` procedures for both core and third-party plugins: Finally, all flags may be omitted to trigger the `install` procedures for both core and third-party plugins:
```shell ```shell

View File

@@ -0,0 +1,15 @@
# 0.36.0 Migration Guide
## Changes
- The `app.json` file format is now parsed as JSONC and supports inline comments.
- The `scheduler-k3s:cluster-add` command is now `scheduler-k3s:cluster:add`.
- The `scheduler-k3s:cluster-list` command is now `scheduler-k3s:cluster:list`.
- The `scheduler-k3s:cluster-remove` command is now `scheduler-k3s:cluster:remove`.
## Removals
- Support for the `DOKKU_DOCKERFILE_CACHE_BUILD` environment variable has been removed. Use the `docker-options` plugin to set build arguments for your app instead.
- Support for the `DOKKU_DOCKER_BUILD_OPTS` environment variable has been removed. Use the `docker-options` plugin to set build arguments for your app instead.
- Support for the `no-cache` nixpacks property has been removed. Use the `docker-options` plugin to set build arguments for your app instead.
- Support for customizing the label-key for the caddy proxy has been removed. Always use `caddy.` as a prefix for your caddy directives.

View File

@@ -21,7 +21,9 @@
(list, optional) A list of cron resources. Keys are the names of the process types. The values are an object containing one or more of the following properties: (list, optional) A list of cron resources. Keys are the names of the process types. The values are an object containing one or more of the following properties:
- `command`: (string, required) - `command`: (string, required)
- `maintenance`: (boolean, optional)
- `schedule`: (string, required) - `schedule`: (string, required)
- `concurrency_policy`: (string, optional, default: `allow`, options: `allow`, `forbid`, `replace`)
## Formation ## Formation

View File

@@ -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.

View File

@@ -110,7 +110,6 @@ The following config variables have special meanings and can be set in a variety
| `DOKKU_DEFAULT_CHECKS_WAIT` | `10` | `dokku config:set` | If no user-defined checks are specified - or if the process being checked is not a `web` process - this is the period of time Dokku will wait before checking that a container is still running. | | `DOKKU_DEFAULT_CHECKS_WAIT` | `10` | `dokku config:set` | If no user-defined checks are specified - or if the process being checked is not a `web` process - this is the period of time Dokku will wait before checking that a container is still running. |
| `DOKKU_DISABLE_PROXY` | none | `dokku proxy:disable` <br /> `dokku proxy:enable` | Disables the proxy in front of your application, resulting in publicly routing the docker container. | | `DOKKU_DISABLE_PROXY` | none | `dokku proxy:disable` <br /> `dokku proxy:enable` | Disables the proxy in front of your application, resulting in publicly routing the docker container. |
| `DOKKU_DISABLE_APP_AUTOCREATION` | none | `dokku config:set` | Disables automatic creation of a non-existent app on deploy. | | `DOKKU_DISABLE_APP_AUTOCREATION` | none | `dokku config:set` | Disables automatic creation of a non-existent app on deploy. |
| `DOKKU_DOCKERFILE_CACHE_BUILD` | none | `dokku config:set` | |
| `DOKKU_DOCKERFILE_START_CMD` | none | `dokku config:set` | | | `DOKKU_DOCKERFILE_START_CMD` | none | `dokku config:set` | |
| `DOKKU_PARALLEL_ARGUMENTS`. | none | `dokku config:set` | Allows passing custom arguments to parallel for `ps:*all` commands | | `DOKKU_PARALLEL_ARGUMENTS`. | none | `dokku config:set` | Allows passing custom arguments to parallel for `ps:*all` commands |
| `DOKKU_PROXY_PORT` | automatically assigned | `/etc/environment` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` <br /> `dokku config:set` | | | `DOKKU_PROXY_PORT` | automatically assigned | `/etc/environment` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` <br /> `dokku config:set` | |

View File

@@ -0,0 +1,174 @@
# Buildpacks Management
> [!NOTE]
> Buildpacks commands apply to both herokuish and CNB-based builds
```
buildpacks:add [--index 1] <app> <buildpack> # Add new app buildpack while inserting into list of buildpacks if necessary
buildpacks:clear <app> # Clear all buildpacks set on the app
buildpacks:list <app> # List all buildpacks for an app
buildpacks:remove <app> <buildpack> # Remove a buildpack set on the app
buildpacks:report [<app>] [<flag>] # Displays a buildpack report for one or more apps
buildpacks:set [--index 1] <app> <buildpack> # Set new app buildpack at a given position defaulting to the first buildpack if no index is specified
```
## Usage
### Listing Buildpacks in Use
The `buildpacks:list` command can be used to show buildpacks that have been set for an app. This will omit any auto-detected buildpacks.
```shell
# running for an app with no buildpacks specified
dokku buildpacks:list node-js-app
```
```
-----> test buildpack urls
```
```shell
# running for an app with two buildpacks specified
dokku buildpacks:list node-js-app
```
```
-----> test buildpack urls
https://github.com/heroku/heroku-buildpack-python.git
https://github.com/heroku/heroku-buildpack-nodejs.git
```
### Adding custom buildpacks
> Please check the documentation for your particular buildpack as you may need to include configuration files (such as a Procfile) in your project root.
To add a custom buildpack, use the `buildpacks:add` command:
```shell
dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-nodejs.git
```
When no buildpacks are currently specified, the specified buildpack will be the only one executed for detection and compilation.
Multiple buildpacks may be specified by using the `buildpacks:add` command multiple times.
```shell
dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-nodejs.git
```
Buildpacks are executed in order, may be inserted at a specified index via the `--index` flag. This flag is specified starting at a 1-index value.
```shell
# will add the golang buildpack at the second position, bumping all proceeding ones by 1 position
dokku buildpacks:add --index 2 node-js-app https://github.com/heroku/heroku-buildpack-golang.git
```
### Overwriting a buildpack position
In some cases, it may be necessary to swap out a given buildpack. Rather than needing to re-specify each buildpack, the `buildpacks:set` command can be used to overwrite a buildpack at a given position.
```shell
dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
```
By default, this will overwrite the _first_ buildpack specified. To specify an index, the `--index` flag may be used. This flag is specified starting at a 1-index value, and defaults to `1`.
```shell
# the following are equivalent commands
dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
dokku buildpacks:set --index 1 node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
```
If the index specified is larger than the number of buildpacks currently configured, the buildpack will be appended to the end of the list.
```shell
dokku buildpacks:set --index 99 node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
```
### Removing a buildpack
> At least one of a buildpack or index must be specified
A single buildpack can be removed by name via the `buildpacks:remove` command.
```shell
dokku buildpacks:remove node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
```
Buildpacks can also be removed by index via the `--index` flag. This flag is specified starting at a 1-index value.
```shell
dokku buildpacks:remove node-js-app --index 1
```
### Clearing all buildpacks
> This does not affect automatically detected buildpacks, nor does it impact any specified `BUILDPACK_URL` environment variable.
The `buildpacks:clear` command can be used to clear all configured buildpacks for a specified app.
```shell
dokku buildpacks:clear node-js-app
```
### Using a specific buildpack version
> Always remember to pin your buildpack versions when using the multi-buildpacks method, or you may find deploys changing your deployed environment.
By default, builders will pin their vendored buildpacks, resulting in a consistent build result for your application. There may be occasions where the pinned version results in a broken deploy, or does not have a particular feature that is required to build your project. To use a more recent version of a given buildpack, the buildpack may be specified _without_ a Git commit SHA like so:
```shell
# using the latest nodejs buildpack
dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-nodejs
```
This will use the latest commit on the `master` branch of the specified buildpack. To pin to a newer version of a buildpack, a sha may also be specified by using the form `REPOSITORY_URL#COMMIT_SHA`, where `COMMIT_SHA` is any tree-ish git object - usually a git tag.
```shell
# using v87 of the nodejs buildpack
dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-nodejs#v87
```
### Displaying buildpack reports for an app
You can get a report about the app's buildpacks status using the `buildpacks:report` command:
```shell
dokku buildpacks:report
```
```
=====> node-js-app buildpacks information
Buildpacks computed stack: gliderlabs/herokuish:v0.7.0-22
Buildpacks global stack: gliderlabs/herokuish:latest-24
Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git
Buildpacks stack: gliderlabs/herokuish:v0.7.0-20
=====> python-sample buildpacks information
Buildpacks computed stack: gliderlabs/herokuish:latest-24
Buildpacks global stack: gliderlabs/herokuish:latest-24
Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git,https://github.com/heroku/heroku-buildpack-python.git
Buildpacks stack:
=====> ruby-sample buildpacks information
Buildpacks computed stack: gliderlabs/herokuish:latest-24
Buildpacks global stack: gliderlabs/herokuish:latest-24
Buildpacks list:
Buildpacks stack:
```
You can run the command for a specific app also.
```shell
dokku buildpacks:report node-js-app
```
```
=====> node-js-app buildpacks information
Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git
```
You can pass flags which will output only the value of the specific information you want. For example:
```shell
dokku buildpacks:report node-js-app --buildpacks-list
```

View File

@@ -14,8 +14,8 @@ buildpacks:set-property [--global|<app>] <key> <value> # Set or clear a buildpa
Cloud Native Buildpacks are an evolution over the Buildpacks technology provided by the Herokuish builder. See the [herokuish buildpacks documentation](/docs/deployment/builders/herokuish-buildpacks.md) for more information on how to clear buildpack build cache for an application. Cloud Native Buildpacks are an evolution over the Buildpacks technology provided by the Herokuish builder. See the [herokuish buildpacks documentation](/docs/deployment/builders/herokuish-buildpacks.md) for more information on how to clear buildpack build cache for an application.
> [!WARNING] > [!NOTE]
> 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. > This functionality uses the `pack` cli from the [Cloud Native Buildpacks](https://buildpacks.io) project to build apps.
## Usage ## Usage
@@ -27,7 +27,7 @@ Builds will proceed with the `pack` cli for the app from then on.
### Caveats ### Caveats
As this functionality is highly experimental, there are a number of caveats. Please note that not all issues are listed below. While pack is well-supported in Dokku, there are a number of caveats. Please note that not all issues are listed below.
- Specifying specific buildpacks is not currently possible. - Specifying specific buildpacks is not currently possible.
- A future release will add support for specifying buildpacks via the `buildpacks` plugin. - A future release will add support for specifying buildpacks via the `buildpacks` plugin.
@@ -41,16 +41,7 @@ As this functionality is highly experimental, there are a number of caveats. Ple
### Detection ### Detection
This builder will be auto-detected in either the following cases: This builder will be auto-detected if a `project.toml` file exists in the root of the app repository. This file is consumed by `pack-cli` and used to describe how the app is built.
- The `DOKKU_CNB_EXPERIMENTAL` app environment variable is set to `1`.
```shell
dokku config:set --no-restart node-js-app DOKKU_CNB_EXPERIMENTAL=1
```
- A `project.toml` file exists in the root of the app repository.
- This file is consumed by `pack-cli` and used to describe how the app is built.
The builder can also be specified via the `builder:set` command: The builder can also be specified via the `builder:set` command:

View File

@@ -199,8 +199,6 @@ dokku config:set node-js-app DOKKU_DOCKERFILE_START_CMD="--harmony server.js"
To tell Docker what to run. To tell Docker what to run.
Setting `$DOKKU_DOCKERFILE_CACHE_BUILD` to `true` or `false` will enable or disable Docker's image layer cache. Lastly, for more granular build control, you may also pass any `docker build` option to `docker`, by setting `$DOKKU_DOCKER_BUILD_OPTS`.
### Procfiles and multiple processes ### Procfiles and multiple processes
> [!IMPORTANT] > [!IMPORTANT]

View File

@@ -4,12 +4,6 @@
> Subcommands new as of 0.15.0 > Subcommands new as of 0.15.0
``` ```
buildpacks:add [--index 1] <app> <buildpack> # Add new app buildpack while inserting into list of buildpacks if necessary
buildpacks:clear <app> # Clear all buildpacks set on the app
buildpacks:list <app> # List all buildpacks for an app
buildpacks:remove <app> <buildpack> # Remove a buildpack set on the app
buildpacks:report [<app>] [<flag>] # Displays a buildpack report for one or more apps
buildpacks:set [--index 1] <app> <buildpack> # Set new app buildpack at a given position defaulting to the first buildpack if no index is specified
buildpacks:set-property [--global|<app>] <key> <value> # Set or clear a buildpacks property for an app buildpacks:set-property [--global|<app>] <key> <value> # Set or clear a buildpacks property for an app
``` ```
@@ -49,104 +43,6 @@ dokku builder:set node-js-app selected herokuish
> Dokku will only select the `dockerfile` builder if both the `herokuish` and `pack` builders are not detected and a Dockerfile exists. See the [dockerfile builder documentation](/docs/deployment/builders/dockerfiles.md) for more information on how that builder functions. > Dokku will only select the `dockerfile` builder if both the `herokuish` and `pack` builders are not detected and a Dockerfile exists. See the [dockerfile builder documentation](/docs/deployment/builders/dockerfiles.md) for more information on how that builder functions.
### Listing Buildpacks in Use
The `buildpacks:list` command can be used to show buildpacks that have been set for an app. This will omit any auto-detected buildpacks.
```shell
# running for an app with no buildpacks specified
dokku buildpacks:list node-js-app
```
```
-----> test buildpack urls
```
```shell
# running for an app with two buildpacks specified
dokku buildpacks:list node-js-app
```
```
-----> test buildpack urls
https://github.com/heroku/heroku-buildpack-python.git
https://github.com/heroku/heroku-buildpack-nodejs.git
```
### Adding custom buildpacks
> Please check the documentation for your particular buildpack as you may need to include configuration files (such as a Procfile) in your project root.
To add a custom buildpack, use the `buildpacks:add` command:
```shell
dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-nodejs.git
```
When no buildpacks are currently specified, the specified buildpack will be the only one executed for detection and compilation.
Multiple buildpacks may be specified by using the `buildpacks:add` command multiple times.
```shell
dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-nodejs.git
```
Buildpacks are executed in order, may be inserted at a specified index via the `--index` flag. This flag is specified starting at a 1-index value.
```shell
# will add the golang buildpack at the second position, bumping all proceeding ones by 1 position
dokku buildpacks:add --index 2 node-js-app https://github.com/heroku/heroku-buildpack-golang.git
```
### Overwriting a buildpack position
In some cases, it may be necessary to swap out a given buildpack. Rather than needing to re-specify each buildpack, the `buildpacks:set` command can be used to overwrite a buildpack at a given position.
```shell
dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
```
By default, this will overwrite the _first_ buildpack specified. To specify an index, the `--index` flag may be used. This flag is specified starting at a 1-index value, and defaults to `1`.
```shell
# the following are equivalent commands
dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
dokku buildpacks:set --index 1 node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
```
If the index specified is larger than the number of buildpacks currently configured, the buildpack will be appended to the end of the list.
```shell
dokku buildpacks:set --index 99 node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
```
### Removing a buildpack
> At least one of a buildpack or index must be specified
A single buildpack can be removed by name via the `buildpacks:remove` command.
```shell
dokku buildpacks:remove node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
```
Buildpacks can also be removed by index via the `--index` flag. This flag is specified starting at a 1-index value.
```shell
dokku buildpacks:remove node-js-app --index 1
```
### Clearing all buildpacks
> This does not affect automatically detected buildpacks, nor does it impact any specified `BUILDPACK_URL` environment variable.
The `buildpacks:clear` command can be used to clear all configured buildpacks for a specified app.
```shell
dokku buildpacks:clear node-js-app
```
### Customizing the Buildpack stack builder ### Customizing the Buildpack stack builder
> [!IMPORTANT] > [!IMPORTANT]
@@ -207,49 +103,6 @@ The default value may be set by passing an empty value for the option.
dokku builder-herokuish:set --global allowed dokku builder-herokuish:set --global allowed
``` ```
### Displaying buildpack reports for an app
You can get a report about the app's buildpacks status using the `buildpacks:report` command:
```shell
dokku buildpacks:report
```
```
=====> node-js-app buildpacks information
Buildpacks computed stack: gliderlabs/herokuish:v0.7.0-22
Buildpacks global stack: gliderlabs/herokuish:latest-24
Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git
Buildpacks stack: gliderlabs/herokuish:v0.7.0-20
=====> python-sample buildpacks information
Buildpacks computed stack: gliderlabs/herokuish:latest-24
Buildpacks global stack: gliderlabs/herokuish:latest-24
Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git,https://github.com/heroku/heroku-buildpack-python.git
Buildpacks stack:
=====> ruby-sample buildpacks information
Buildpacks computed stack: gliderlabs/herokuish:latest-24
Buildpacks global stack: gliderlabs/herokuish:latest-24
Buildpacks list:
Buildpacks stack:
```
You can run the command for a specific app also.
```shell
dokku buildpacks:report node-js-app
```
```
=====> node-js-app buildpacks information
Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git
```
You can pass flags which will output only the value of the specific information you want. For example:
```shell
dokku buildpacks:report node-js-app --buildpacks-list
```
### Displaying builder-herokuish reports for an app ### Displaying builder-herokuish reports for an app
> [!IMPORTANT] > [!IMPORTANT]
@@ -309,24 +162,6 @@ If an application was previously deployed via Dockerfile, the following commands
dokku ports:clear node-js-app dokku ports:clear node-js-app
``` ```
### Using a specific buildpack version
> Always remember to pin your buildpack versions when using the multi-buildpacks method, or you may find deploys changing your deployed environment.
By default, Dokku uses the [gliderlabs/herokuish](https://github.com/gliderlabs/herokuish/) project, which pins all of it's vendored buildpacks. There may be occasions where the pinned version results in a broken deploy, or does not have a particular feature that is required to build your project. To use a more recent version of a given buildpack, the buildpack may be specified _without_ a Git commit SHA like so:
```shell
# using the latest nodejs buildpack
dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-nodejs
```
This will use the latest commit on the `master` branch of the specified buildpack. To pin to a newer version of a buildpack, a sha may also be specified by using the form `REPOSITORY_URL#COMMIT_SHA`, where `COMMIT_SHA` is any tree-ish git object - usually a git tag.
```shell
# using v87 of the nodejs buildpack
dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-nodejs#v87
```
### `curl` build timeouts ### `curl` build timeouts
Certain buildpacks may time out in retrieving dependencies via `curl`. This can happen when your network connection is poor or if there is significant network congestion. You may see a message similar to `gzip: stdin: unexpected end of file` after a `curl` command. Certain buildpacks may time out in retrieving dependencies via `curl`. This can happen when your network connection is poor or if there is significant network congestion. You may see a message similar to `gzip: stdin: unexpected end of file` after a `curl` command.
@@ -345,3 +180,32 @@ See the [repository management documentation](/docs/advanced-usage/repository-ma
### Specifying commands via Procfile ### Specifying commands via Procfile
See the [Procfile documentation](/docs/processes/process-management.md#procfile) for more information on how to specify different processes for your app. See the [Procfile documentation](/docs/processes/process-management.md#procfile) for more information on how to specify different processes for your app.
### Listing Buildpacks in Use
See the [buildpack management documentation](/docs/processes/process-management.md#listing-buildpacks-in-use) for more information on how to list buildpacks in use.
### Adding custom buildpacks
See the [buildpack management documentation](/docs/processes/process-management.md#adding-custom-buildpacks) for more information on how to add custom buildpacks.
### Overwriting a buildpack position
See the [buildpack management documentation](/docs/processes/process-management.md#overwriting-a-buildpack-position) for more information on how to overwrite a buildpack position.
### Removing a buildpack
See the [buildpack management documentation](/docs/processes/process-management.md#removing-a-buildpack) for more information on how to remove a buildpack.
### Clearing all buildpacks
See the [buildpack management documentation](/docs/processes/process-management.md#clearing-all-buildpacks) for more information on how to clear all buildpacks.
### Using a specific buildpack version
See the [buildpack management documentation](/docs/processes/process-management.md#using-a-specific-buildpack-version) for more information on how to using a specific buildpack version
### Displaying buildpack reports for an app
See the [buildpack management documentation](/docs/processes/process-management.md#displaying-buildpack-reports-for-an-app) for more information on how to display buildpack reports for an app.

View File

@@ -80,28 +80,10 @@ dokku builder-nixpacks:set --global nixpackstoml-path
### Disabling cache ### Disabling cache
Cache is enabled by default, but can be disabled by setting the `no-cache` property to `true`: Disable cache using the [docker-options] plugin:
```shell ```shell
dokku builder-nixpacks:set node-js-app no-cache true dokku docker-options:add node-js-app build "--no-cache"
```
The default value may be set by passing an empty value for the option:
```shell
dokku builder-nixpacks: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-nixpacks:set --global no-cache true
```
The default value may be set by passing an empty value for the option.
```shell
dokku builder-nixpacks:set --global no-cache
``` ```
### Displaying builder-nixpacks reports for an app ### Displaying builder-nixpacks reports for an app
@@ -117,23 +99,14 @@ dokku builder-nixpacks:report
Builder-nixpacks computed nixpackstoml path: nixpacks2.toml Builder-nixpacks computed nixpackstoml path: nixpacks2.toml
Builder-nixpacks global nixpackstoml path: nixpacks.toml Builder-nixpacks global nixpackstoml path: nixpacks.toml
Builder-nixpacks nixpackstoml path: nixpacks2.toml Builder-nixpacks nixpackstoml path: nixpacks2.toml
Builder-nixpacks computed no cache: true
Builder-nixpacks global no cache: false
Builder-nixpacks no cache: true
=====> python-sample builder-nixpacks information =====> python-sample builder-nixpacks information
Builder-nixpacks computed nixpackstoml path: nixpacks.toml Builder-nixpacks computed nixpackstoml path: nixpacks.toml
Builder-nixpacks global nixpackstoml path: nixpacks.toml Builder-nixpacks global nixpackstoml path: nixpacks.toml
Builder-nixpacks nixpackstoml path: Builder-nixpacks nixpackstoml path:
Builder-nixpacks computed no cache: false
Builder-nixpacks global no cache: false
Builder-nixpacks no cache:
=====> ruby-sample builder-nixpacks information =====> ruby-sample builder-nixpacks information
Builder-nixpacks computed nixpackstoml path: nixpacks.toml Builder-nixpacks computed nixpackstoml path: nixpacks.toml
Builder-nixpacks global nixpackstoml path: nixpacks.toml Builder-nixpacks global nixpackstoml path: nixpacks.toml
Builder-nixpacks nixpackstoml path: Builder-nixpacks nixpackstoml path:
Builder-nixpacks computed no cache: false
Builder-nixpacks global no cache: false
Builder-nixpacks no cache:
``` ```
You can run the command for a specific app also. You can run the command for a specific app also.
@@ -147,17 +120,14 @@ dokku builder-nixpacks:report node-js-app
Builder-nixpacks computed nixpackstoml path: nixpacks2.toml Builder-nixpacks computed nixpackstoml path: nixpacks2.toml
Builder-nixpacks global nixpackstoml path: nixpacks.toml Builder-nixpacks global nixpackstoml path: nixpacks.toml
Builder-nixpacks nixpackstoml path: nixpacks2.toml Builder-nixpacks nixpackstoml path: nixpacks2.toml
Builder-nixpacks computed no cache: true
Builder-nixpacks global no cache: false
Builder-nixpacks no cache: true
``` ```
You can pass flags which will output only the value of the specific information you want. For example: You can pass flags which will output only the value of the specific information you want. For example:
```shell ```shell
dokku builder-nixpacks:report node-js-app --builder-nixpacks-no-cache dokku builder-nixpacks:report node-js-app --builder-nixpacks-nixpackstoml-path
``` ```
``` ```
true nixpacks2.toml
``` ```

View File

@@ -0,0 +1,142 @@
# Railpack
> [!IMPORTANT]
> New as of 0.37.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
Disable cache using the [docker-options] plugin:
```shell
dokku docker-options:add node-js-app build "--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
=====> python-sample builder-railpack information
Builder-railpack computed railpackjson path: railpack.json
Builder-railpack global railpackjson path: railpack.json
Builder-railpack railpackjson path:
=====> ruby-sample builder-railpack information
Builder-railpack computed railpackjson path: railpack.json
Builder-railpack global railpackjson path: railpack.json
Builder-railpack railpackjson path:
```
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
```
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-railpackjson-path
```
```
railpack2.json
```

View File

@@ -71,7 +71,7 @@ All commands have the application name automatically set via the `--app` flag on
The client supports several environment variables: The client supports several environment variables:
- `DOKKU_HOST` (default: `dokku` git remote): Used to interact with a specific remote server. Can be overridden via the `--remote` flag. - `DOKKU_HOST` (default: `dokku` git remote): Used to interact with a specific remote server. Can be overridden via the `--remote` flag. Must specify the `--app` flag when specified.
- `DOKKU_PORT` (default: `22`): Used to specify a port to connect to the Dokku server on. - `DOKKU_PORT` (default: `22`): Used to specify a port to connect to the Dokku server on.
It also supports several flags (all flags unspecified here are passed as is to the server): It also supports several flags (all flags unspecified here are passed as is to the server):
@@ -99,6 +99,12 @@ To list all available remotes, run `dokku remote:list`. This is equivalent to ru
To add a remote, run `dokku remote:add $REMOTE_NAME dokku@dokku.me:$APP_NAME`, replacing `$REMOTE_NAME` with your desired remote name, the `$APP_NAME` with your desired app name and the host `dokku.me` with your Dokku host. This is equivalent to running `git remote add $REMOTE_NAME dokku@dokku.me:$APP_NAME`. To add a remote, run `dokku remote:add $REMOTE_NAME dokku@dokku.me:$APP_NAME`, replacing `$REMOTE_NAME` with your desired remote name, the `$APP_NAME` with your desired app name and the host `dokku.me` with your Dokku host. This is equivalent to running `git remote add $REMOTE_NAME dokku@dokku.me:$APP_NAME`.
The following remote formats are supported:
- `dokku@HOSTNAME.TLD:APP_NAME`
- `ssh://dokku@HOSTNAME.TLD/APP_NAME`
- `ssh://dokku@HOSTNAME.TLD:HOST_PORT/APP_NAME`
To remove a remote, run `dokku remote:remove $REMOTE_NAME`. This is equivalent to running `git remote remove $REMOTE_NAME`. To remove a remote, run `dokku remote:remove $REMOTE_NAME`. This is equivalent to running `git remote remove $REMOTE_NAME`.
By default, the remote in use is `dokku`. To change the remote the client uses by default, run `dokku remote:set $REMOTE_NAME`, where `$REMOTE_NAME` is the name of your desired remote. This will not check the value specified, so that remote *must* be available or errors will occur when running normal client commands. Note that specifying `--remote` when running dokku commands will override this configuration value. By default, the remote in use is `dokku`. To change the remote the client uses by default, run `dokku remote:set $REMOTE_NAME`, where `$REMOTE_NAME` is the name of your desired remote. This will not check the value specified, so that remote *must* be available or errors will occur when running normal client commands. Note that specifying `--remote` when running dokku commands will override this configuration value.

View File

@@ -7,12 +7,15 @@
scheduler-k3s:annotations:set <app|--global> <property> (<value>) [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE>, Set or clear an annotation for a given app/process-type/resource-type combination scheduler-k3s:annotations:set <app|--global> <property> (<value>) [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE>, Set or clear an annotation for a given app/process-type/resource-type combination
scheduler-k3s:autoscaling-auth:set <app|--global> <trigger> [<--metadata key=value>...], Set or clear a scheduler-k3s autoscaling keda trigger authentication resource for an app scheduler-k3s:autoscaling-auth:set <app|--global> <trigger> [<--metadata key=value>...], Set or clear a scheduler-k3s autoscaling keda trigger authentication resource for an app
scheduler-k3s:autoscaling-auth:report <app|--global> [--format stdout|json] [--include-metadata] # Displays a scheduler-k3s autoscaling auth report for an app scheduler-k3s:autoscaling-auth:report <app|--global> [--format stdout|json] [--include-metadata] # Displays a scheduler-k3s autoscaling auth report for an app
scheduler-k3s:cluster-add [ssh://user@host:port] # Adds a server node to a Dokku-managed cluster scheduler-k3s:cluster:add [ssh://user@host:port] # Adds a server node to a Dokku-managed cluster
scheduler-k3s:cluster-list # Lists all nodes in a Dokku-managed cluster scheduler-k3s:cluster:list # Lists all nodes in a Dokku-managed cluster
scheduler-k3s:cluster-remove [node-id] # Removes client node to a Dokku-managed cluster scheduler-k3s:cluster:remove [node-id] # Removes client node to a Dokku-managed cluster
scheduler-k3s:ensure-charts # Ensures the k3s charts are installed scheduler-k3s:ensure-charts # Ensures the k3s charts are installed
scheduler-k3s:initialize # Initializes a cluster scheduler-k3s:initialize # Initializes a cluster
scheduler-k3s:labels:set <app|--global> <property> (<value>) [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE>, Set or clear a label for a given app/process-type/resource-type combination scheduler-k3s:labels:set <app|--global> <property> (<value>) [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE> # Set or clear a label for a given app/process-type/resource-type combination
scheduler-k3s:profiles:add <profile> [--role ROLE] [--insecure-allow-unknown-hosts] [--taint-scheduling] [--kubelet-args KUBELET_ARGS] Adds a node profile to the k3s cluster
scheduler-k3s:profiles:list [--format json|stdout] # Lists all node profiles in the k3s cluster
scheduler-k3s:profiles:remove <profile> # Removes a node profile from the k3s cluster
scheduler-k3s:report [<app>] [<flag>] # Displays a scheduler-k3s report for one or more apps scheduler-k3s:report [<app>] [<flag>] # Displays a scheduler-k3s report for one or more apps
scheduler-k3s:set [<app>|--global] <key> (<value>) # Set or clear a scheduler-k3s property for an app or the scheduler scheduler-k3s:set [<app>|--global] <key> (<value>) # Set or clear a scheduler-k3s property for an app or the scheduler
scheduler-k3s:show-kubeconfig # Displays the kubeconfig for remote usage scheduler-k3s:show-kubeconfig # Displays the kubeconfig for remote usage
@@ -77,22 +80,38 @@ dokku scheduler-k3s:initialize --ingress-class traefik
#### Adding a worker node #### Adding a worker node
Nodes that run app workloads can be added via the `scheduler-k3s:cluster-add` command. This will ssh onto the specified server, install k3s, and join it to the current Dokku node in worker mode. Workers are typically used to run app workloads. Nodes that run app workloads can be added via the `scheduler-k3s:cluster:add` command. This will ssh onto the specified server, install k3s, and join it to the current Dokku node in worker mode. Workers are typically used to run app workloads.
```shell ```shell
dokku scheduler-k3s:cluster-add ssh://root@worker-1.example.com dokku scheduler-k3s:cluster:add ssh://root@worker-1.example.com
```
Per-node kubelet flags can be supplied by passing `--kubelet-args` with a comma-separated `key=value` list. This is useful for tuning scheduler capacity or enforcing cluster-wide defaults at the node level.
```shell
dokku scheduler-k3s:cluster:add \
--kubelet-args allowed-unsafe-sysctls=net.ipv6.conf.all.disable_ipv6 \
ssh://root@worker-1.example.com
```
Multiple kubelet arguments can be specified in the same call by separating them with commas. The following example enables IPv4 forwarding while also increasing the pod density on the worker.
```shell
dokku scheduler-k3s:cluster:add \
--kubelet-args allowed-unsafe-sysctls=net.ipv6.conf.all.disable_ipv6,max-pods=150 \
ssh://root@worker-2.example.com
``` ```
If the server isn't in the `known_hosts` file, the connection will fail. This can be bypassed by setting the `--insecure-allow-unknown-hosts` flag: If the server isn't in the `known_hosts` file, the connection will fail. This can be bypassed by setting the `--insecure-allow-unknown-hosts` flag:
```shell ```shell
dokku scheduler-k3s:cluster-add --insecure-allow-unknown-hosts ssh://root@worker-1.example.com dokku scheduler-k3s:cluster:add --insecure-allow-unknown-hosts ssh://root@worker-1.example.com
``` ```
By default, Dokku will attempt to auto-detect the IP address of the Dokku server for the remote server to connect to. In cases where the auto-detected IP address is incorrect, an override may be specified via the `--server-ip` flag: By default, Dokku will attempt to auto-detect the IP address of the Dokku server for the remote server to connect to. In cases where the auto-detected IP address is incorrect, an override may be specified via the `--server-ip` flag:
```shell ```shell
dokku scheduler-k3s:cluster-add --server-ip 192.168.20.15 ssh://root@worker-1.example.com dokku scheduler-k3s:cluster:add --server-ip 192.168.20.15 ssh://root@worker-1.example.com
``` ```
#### Adding a server node #### Adding a server node
@@ -102,28 +121,28 @@ dokku scheduler-k3s:cluster-add --server-ip 192.168.20.15 ssh://root@worker-1.ex
Server nodes are typically used to replicate the cluster state, and it is recommended to have an odd number of nodes spread across several availability zones (datacenters in close proximity within a region). This allows for higher availability in the event of a cluster failure. Server nodes run control-plane services such as the traefik load balancer and the etcd backing store. Server nodes are typically used to replicate the cluster state, and it is recommended to have an odd number of nodes spread across several availability zones (datacenters in close proximity within a region). This allows for higher availability in the event of a cluster failure. Server nodes run control-plane services such as the traefik load balancer and the etcd backing store.
Server nodes can also be added with the `scheduler-k3s:cluster-add` command by specifying `--role server`. This will ssh onto the specified server, install k3s, and join it to the current Dokku node in server mode. Server nodes can also be added with the `scheduler-k3s:cluster:add` command by specifying `--role server`. This will ssh onto the specified server, install k3s, and join it to the current Dokku node in server mode.
```shell ```shell
dokku scheduler-k3s:cluster-add --role server ssh://root@server-1.example.com dokku scheduler-k3s:cluster:add --role server ssh://root@server-1.example.com
``` ```
Server nodes allow any workloads to be scheduled on them by default, in addition to the control-plane, etcd, and the scheduler itself. To avoid app workloads being scheduled on your control-plane, use the `--taint-scheduling` flag: Server nodes allow any workloads to be scheduled on them by default, in addition to the control-plane, etcd, and the scheduler itself. To avoid app workloads being scheduled on your control-plane, use the `--taint-scheduling` flag:
```shell ```shell
dokku scheduler-k3s:cluster-add --role server --taint-scheduling ssh://root@server-1.example.com dokku scheduler-k3s:cluster:add --role server --taint-scheduling ssh://root@server-1.example.com
``` ```
If the server isn't in the `known_hosts` file, the connection will fail. This can be bypassed by setting the `--insecure-allow-unknown-hosts` flag: If the server isn't in the `known_hosts` file, the connection will fail. This can be bypassed by setting the `--insecure-allow-unknown-hosts` flag:
```shell ```shell
dokku scheduler-k3s:cluster-add --role server --insecure-allow-unknown-hosts ssh://root@server-1.example.com dokku scheduler-k3s:cluster:add --role server --insecure-allow-unknown-hosts ssh://root@server-1.example.com
``` ```
By default, Dokku will attempt to auto-detect the IP address of the Dokku server for the remote server to connect to. In cases where the auto-detected IP address is incorrect, an override may be specified via the `--server-ip` flag: By default, Dokku will attempt to auto-detect the IP address of the Dokku server for the remote server to connect to. In cases where the auto-detected IP address is incorrect, an override may be specified via the `--server-ip` flag:
```shell ```shell
dokku scheduler-k3s:cluster-add --role server --server-ip 192.168.20.15 ssh://root@server-1.example.com dokku scheduler-k3s:cluster:add --role server --server-ip 192.168.20.15 ssh://root@server-1.example.com
``` ```
#### Changing the network interface #### Changing the network interface
@@ -134,6 +153,48 @@ When attaching an worker or server node, the K3s plugin will look at the IP asso
dokku scheduler-k3s:set --global network-interface eth1 dokku scheduler-k3s:set --global network-interface eth1
``` ```
### Node Profiles
Node profiles capture repeatable `scheduler-k3s:cluster:add` options so you can join multiple nodes with identical settings. A profile name can be specified for the `scheduler-k3s:cluster:add` command via the `--profile <name>` flag. Any flags passed directly to `scheduler-k3s:cluster:add` override the stored values for that run.
#### Listing profiles
Display stored profiles to understand which roles and behaviors will be used.
```shell
dokku scheduler-k3s:profiles:list
```
```
name role
awesome-profile worker
```
This command also takes an optional `--format` flag to specify a format for the output. Options include `json` and `stdout`
#### Adding profiles
Create or update a profile that defines how new nodes should be prepared before joining the cluster.
```shell
dokku scheduler-k3s:profiles:add edge-workers \
--role worker \
--insecure-allow-unknown-hosts \
--kubelet-args protect-kernel-defaults=true,eviction-hard=memory.available<200Mi
```
Profile names must be alphanumeric, may include internal dashes, cannot start/end with a dash, and must be ≤32 characters. Other than the `--server-ip` flag, all flags used for `scheduler-k3s:cluster:add` are valid for the `scheduler-k3s:profiles:add` command.
#### scheduler-k3s:profiles:remove
Delete a profile once it’s no longer required.
```shell
dokku scheduler-k3s:profiles:remove edge-workers
```
Removal only deletes the stored definition; nodes that already joined the cluster keep their existing configuration.
### Changing deployment settings ### Changing deployment settings
The k3s plugin provides a number of settings that can be used to managed deployments on a per-app basis. The following table outlines ones not covered elsewhere: The k3s plugin provides a number of settings that can be used to managed deployments on a per-app basis. The following table outlines ones not covered elsewhere:

View File

@@ -2805,6 +2805,26 @@ DOKKU_SCHEDULER="$1"; APP="$2"; CONTAINER="$3"; TAIL="$4"; PRETTY_PRINT="$5"; NU
# TODO # TODO
``` ```
### `scheduler-run-retire`
> [!WARNING]
> The scheduler plugin trigger apis are under development and may change
> between minor releases until the 1.0 release.
- Description: Allows you to retire containers started by the `run` command
- Invoked by: `dokku run:retire`
- Arguments: `$DOKKU_SCHEDULER $APP`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
DOKKU_SCHEDULER="$1"; APP="$2";
# TODO
```
### `scheduler-run-stop` ### `scheduler-run-stop`
> [!WARNING] > [!WARNING]

View File

@@ -84,8 +84,8 @@ The alternative is to build a custom docker image via a custom Dockerfile. This
```Dockerfile ```Dockerfile
FROM dokku/dokku:0.36.11 FROM dokku/dokku:0.36.11
RUN dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres RUN dokku plugin:install https://github.com/dokku/dokku-postgres.git --skip-install-trigger
RUN dokku plugin:install https://github.com/dokku/dokku-redis.git redis RUN dokku plugin:install https://github.com/dokku/dokku-redis.git --skip-install-trigger
``` ```
## SSH Key Management ## SSH Key Management

View File

@@ -18,6 +18,8 @@ Docker releases updates periodically to their engine. We recommend reading their
Before upgrading, check the migration guides to get comfortable with new features and prepare your deployment to be upgraded. Before upgrading, check the migration guides to get comfortable with new features and prepare your deployment to be upgraded.
- [Upgrading to 0.37](/docs/appendices/0.37.0-migration-guide.md)
- [Upgrading to 0.36](/docs/appendices/0.36.0-migration-guide.md)
- [Upgrading to 0.35](/docs/appendices/0.35.0-migration-guide.md) - [Upgrading to 0.35](/docs/appendices/0.35.0-migration-guide.md)
- [Upgrading to 0.34](/docs/appendices/0.34.0-migration-guide.md) - [Upgrading to 0.34](/docs/appendices/0.34.0-migration-guide.md)
- [Upgrading to 0.33](/docs/appendices/0.33.0-migration-guide.md) - [Upgrading to 0.33](/docs/appendices/0.33.0-migration-guide.md)

View File

@@ -3,7 +3,7 @@
> [!IMPORTANT] > [!IMPORTANT]
> New as of 0.28.0 > New as of 0.28.0
Dokku provides integration with the [Caddy](https://caddyserver.com/) proxy service by utilizing the Docker label-based integration implemented by Caddy. Dokku provides integration with the [Caddy](https://caddyserver.com/) proxy service by utilizing the Docker label-based integration implemented by [Caddy Docker Proxy](https://github.com/lucaslorentz/caddy-docker-proxy).
``` ```
caddy:report [<app>] [<flag>] # Displays a caddy report for one or more apps caddy:report [<app>] [<flag>] # Displays a caddy report for one or more apps
@@ -117,15 +117,53 @@ dokku caddy:set --global log-level DEBUG
After modifying, the Caddy container will need to be restarted. After modifying, the Caddy container will need to be restarted.
### Changing the label key for the app ### Label Management
The default label key for the app is `caddy`. This can be changed by setting the `label-key` property: The Caddy plugin allows you to add custom container labels to apps. These labels are injected into containers during deployment and can be used to configure Caddy behavior beyond what the plugin provides by default.
Refer to the upstream [caddy-docker-proxy](https://github.com/lucaslorentz/caddy-docker-proxy) documentation for more information on what labels are available.
#### Adding a label
To add a custom container label to an app, use the `caddy:labels:add` command:
```shell ```shell
dokku caddy:set node-js-app label-key caddy_0 dokku caddy:labels:add node-js-app caddy.directive value
``` ```
This will update the app's label key to `caddy_0`. The label key is used to identify the app in the Caddy configuration. If you change the label key, you will need to restart or rebuild the app to apply the updates. This will add the label `caddy.directive=value` to the app's containers. After adding a label, you will need to rebuild or redeploy the app for the label to be applied to running containers.
```shell
dokku ps:rebuild node-js-app
```
#### Removing a label
To remove a custom container label from an app, use the `caddy:labels:remove` command:
```shell
dokku caddy:labels:remove node-js-app caddy.directive
```
This will remove the specified label from the app. After removing a label, you will need to rebuild or redeploy the app for the change to be applied to running containers.
```shell
dokku ps:rebuild node-js-app
```
#### Showing labels
To view all custom container labels for an app, use the `caddy:labels:show` command:
```shell
dokku caddy:labels:show node-js-app
```
To view a specific label value, provide the label name:
```shell
dokku caddy:labels:show node-js-app caddy.directive
```
### SSL Configuration ### SSL Configuration

View File

@@ -113,6 +113,54 @@ dokku haproxy:set --global log-level DEBUG
After modifying, the Haproxy container will need to be restarted. After modifying, the Haproxy container will need to be restarted.
### Label Management
The Haproxy plugin allows you to add custom container labels to apps. These labels are injected into containers during deployment and can be used to configure Haproxy behavior beyond what the plugin provides by default.
Refer to the upstream [EasyHaproxy](https://github.com/byjg/docker-easy-haproxy) documentation for more information on what labels are available.
#### Adding a label
To add a custom container label to an app, use the `haproxy:labels:add` command:
```shell
dokku haproxy:labels:add node-js-app haproxy.directive value
```
This will add the label `haproxy.directive=value` to the app's containers. After adding a label, you will need to rebuild or redeploy the app for the label to be applied to running containers.
```shell
dokku ps:rebuild node-js-app
```
#### Removing a label
To remove a custom container label from an app, use the `haproxy:labels:remove` command:
```shell
dokku haproxy:labels:remove node-js-app haproxy.directive
```
This will remove the specified label from the app. After removing a label, you will need to rebuild or redeploy the app for the change to be applied to running containers.
```shell
dokku ps:rebuild node-js-app
```
#### Showing labels
To view all custom container labels for an app, use the `haproxy:labels:show` command:
```shell
dokku haproxy:labels:show node-js-app
```
To view a specific label value, provide the label name:
```shell
dokku haproxy:labels:show node-js-app haproxy.directive
```
### SSL Configuration ### SSL Configuration
The haproxy plugin only supports automatic ssl certificates from it's letsencrypt integration. Managed certificates provided by the `certs` plugin are ignored. The haproxy plugin only supports automatic ssl certificates from it's letsencrypt integration. Managed certificates provided by the `certs` plugin are ignored.

View File

@@ -124,6 +124,54 @@ The following folders within an app repository may have `*.conf` files that will
- `openresty/http-includes/`: Injected in the `server` block serving http(s) requests for the app. - `openresty/http-includes/`: Injected in the `server` block serving http(s) requests for the app.
- `openresty/http-location-includes/`: Injected in the `location` block that proxies to the app in the app's respective `server` block. - `openresty/http-location-includes/`: Injected in the `location` block that proxies to the app in the app's respective `server` block.
### Label Management
The OpenResty plugin allows you to add custom container labels to apps. These labels are injected into containers during deployment and can be used to configure OpenResty behavior beyond what the plugin provides by default.
Refer to the upstream [openresty-docker-proxy](https://github.com/dokku/openresty-docker-proxy) documentation for more information on what labels are available.
#### Adding a label
To add a custom container label to an app, use the `openresty:labels:add` command:
```shell
dokku openresty:labels:add node-js-app openresty.directive value
```
This will add the label `openresty.directive=value` to the app's containers. After adding a label, you will need to rebuild or redeploy the app for the label to be applied to running containers.
```shell
dokku ps:rebuild node-js-app
```
#### Removing a label
To remove a custom container label from an app, use the `openresty:labels:remove` command:
```shell
dokku openresty:labels:remove node-js-app openresty.directive
```
This will remove the specified label from the app. After removing a label, you will need to rebuild or redeploy the app for the change to be applied to running containers.
```shell
dokku ps:rebuild node-js-app
```
#### Showing labels
To view all custom container labels for an app, use the `openresty:labels:show` command:
```shell
dokku openresty:labels:show node-js-app
```
To view a specific label value, provide the label name:
```shell
dokku openresty:labels:show node-js-app openresty.directive
```
### SSL Configuration ### SSL Configuration
The OpenResty plugin only supports automatic ssl certificates from it's letsencrypt integration. Managed certificates provided by the `certs` plugin are ignored. The OpenResty plugin only supports automatic ssl certificates from it's letsencrypt integration. Managed certificates provided by the `certs` plugin are ignored.

View File

@@ -127,6 +127,54 @@ dokku traefik:set --global log-level DEBUG
After modifying, the Traefik container will need to be restarted. After modifying, the Traefik container will need to be restarted.
### Label Management
The Traefik plugin allows you to add custom container labels to apps. These labels are injected into containers during deployment and can be used to configure Traefik behavior beyond what the plugin provides by default.
Refer to the upstream [Traefik](https://doc.traefik.io/traefik/) documentation for more information on what labels are available.
#### Adding a label
To add a custom container label to an app, use the `traefik:labels:add` command:
```shell
dokku traefik:labels:add node-js-app traefik.directive value
```
This will add the label `traefik.directive=value` to the app's containers. After adding a label, you will need to rebuild or redeploy the app for the label to be applied to running containers.
```shell
dokku ps:rebuild node-js-app
```
#### Removing a label
To remove a custom container label from an app, use the `traefik:labels:remove` command:
```shell
dokku traefik:labels:remove node-js-app traefik.directive
```
This will remove the specified label from the app. After removing a label, you will need to rebuild or redeploy the app for the change to be applied to running containers.
```shell
dokku ps:rebuild node-js-app
```
#### Showing labels
To view all custom container labels for an app, use the `traefik:labels:show` command:
```shell
dokku traefik:labels:show node-js-app
```
To view a specific label value, provide the label name:
```shell
dokku traefik:labels:show node-js-app traefik.directive
```
### SSL Configuration ### SSL Configuration
The traefik plugin only supports automatic ssl certificates from it's letsencrypt integration. Managed certificates provided by the `certs` plugin are ignored. The traefik plugin only supports automatic ssl certificates from it's letsencrypt integration. Managed certificates provided by the `certs` plugin are ignored.

View File

@@ -1,8 +1,8 @@
# One-off Tasks # One-off Tasks
``` ```
run [-e|--env KEY=VALUE] [--no-tty] <app> <cmd> # Run a command in a new container using the current app image run [-e|--env KEY=VALUE] [--no-tty] [--ttl-seconds SECONDS] <app> <cmd> # Run a command in a new container using the current app image
run:detached [-e|-env KEY=VALUE] [--force-tty] <app> <cmd> # Run a command in a new detached container using the current app image run:detached [-e|-env KEY=VALUE] [--force-tty] [--ttl-seconds SECONDS] <app> <cmd> # Run a command in a new detached container using the current app image
run:list [--format json|stdout] [<app>] # List all run containers for an app run:list [--format json|stdout] [<app>] # List all run containers for an app
run:logs <app|--container CONTAINER> [-h] [-t] [-n num] [-q] # Display recent log output for run containers run:logs <app|--container CONTAINER> [-h] [-t] [-n num] [-q] # Display recent log output for run containers
run:stop <app|--container CONTAINER> # Stops all run containers for an app or a specified run container run:stop <app|--container CONTAINER> # Stops all run containers for an app or a specified run container
@@ -29,6 +29,16 @@ dokku run --env "NODE_ENV=development" --env "PATH=/custom/path" node-js-app npm
One off containers are removed at the end of process execution. One off containers are removed at the end of process execution.
In addition, one-off containers also run for a max of 24 hours (86400 seconds) by default, after which they are reaped. This can be changed by setting the `--ttl-seconds` argument:
```shell
# runs for 10 minutes
dokku run --ttl-seconds 600 node-js-app npm run mytask
```
One-off containers that exceed their runtime are reaped every 5 minutes, so the ttl-seconds is an approximation and your app may end up running for up to 5 minutes longer than expected.
#### Running Procfile commands #### Running Procfile commands
The `run` command can also be used to run a command defined in the app `Procfile`: The `run` command can also be used to run a command defined in the app `Procfile`:

View File

@@ -6,8 +6,10 @@
``` ```
cron:list <app> [--format json|stdout] # List scheduled cron tasks for an app cron:list <app> [--format json|stdout] # List scheduled cron tasks for an app
cron:report [<app>] [<flag>] # Display report about an app cron:report [<app>] [<flag>] # Display report about an app
cron:resume <app> <cron_id> # Resume a cron task
cron:run <app> <cron_id> [--detach] # Run a cron task on the fly cron:run <app> <cron_id> [--detach] # Run a cron task on the fly
cron:set [--global|<app>] <key> <value> # Set or clear a cron property for an app cron:set [--global|<app>] <key> <value> # Set or clear a cron property for an app
cron:suspend <app> <cron_id> # Suspend a cron task
``` ```
## Usage ## Usage
@@ -34,10 +36,15 @@ The `app.json` file for a given app can define a special `cron` key that contain
A cron task takes the following properties: A cron task takes the following properties:
- `command`: A command to be run within the built app image. Specified commands can also be `Procfile` entries. - `command`: A command to be run within the built app image. Specified commands can also be `Procfile` entries.
- `maintenance`: A boolean value that decides whether the cron task is in maintenance and therefore executable or not.
- `schedule`: A [cron-compatible](https://en.wikipedia.org/wiki/Cron#Overview) scheduling definition upon which to run the command. Seconds are generally not supported. - `schedule`: A [cron-compatible](https://en.wikipedia.org/wiki/Cron#Overview) scheduling definition upon which to run the command. Seconds are generally not supported.
- `concurrency_policy`: A string (default: `allow`), that controls whether the cron task can be run concurrently with another invocation of itself. Valid options are `allow` (allow concurrency), `forbid` (exit the new cron task if there is an existing one), `replace` (delete any existing cron task and start the new one).
Zero or more cron tasks can be specified per app. Cron tasks are validated after the build artifact is created but before the app is deployed, and the cron schedule is updated during the post-deploy phase. Zero or more cron tasks can be specified per app. Cron tasks are validated after the build artifact is created but before the app is deployed, and the cron schedule is updated during the post-deploy phase.
Cron tasks can run for a maximum of 24 hours via the docker-local scheduler, after which they are reaped from the system.
See the [app.json location documentation](/docs/advanced-usage/deployment-tasks.md#changing-the-appjson-location) for more information on where to place your `app.json` file. See the [app.json location documentation](/docs/advanced-usage/deployment-tasks.md#changing-the-appjson-location) for more information on where to place your `app.json` file.
#### Task Environment #### Task Environment
@@ -123,6 +130,26 @@ ID Schedule Command
5cruaotm4yzzpnjlsdunblj8qyjp @daily /bin/true 5cruaotm4yzzpnjlsdunblj8qyjp @daily /bin/true
``` ```
#### Suspending and resuming a specific cron task
Cron tasks can be suspended to temporarily prevent them from running, and later resumed to re-enable them. This is useful for maintenance or debugging purposes.
To suspend a specific cron task, use the `cron:suspend` command with the app name and cron ID:
```shell
dokku cron:suspend node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5
```
A suspended task will not execute according to its schedule. You can verify a task is suspended by checking the `Maintenance` column in the `cron:list` output, which will show `true (task)` for suspended tasks.
To resume a suspended cron task, use the `cron:resume` command:
```shell
dokku cron:resume node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5
```
Once resumed, the task will execute according to its schedule again. The cron ID can be retrieved from the `cron:list` output.
#### Executing a cron task on the fly #### Executing a cron task on the fly
Cron tasks can be invoked via the `cron:run` command. This command takes an `app` argument and a `cron id` (retrievable from `cron:list` output). Cron tasks can be invoked via the `cron:run` command. This command takes an `app` argument and a `cron id` (retrievable from `cron:list` output).

View File

@@ -150,6 +150,7 @@
<a href="/{{NAME}}/deployment/builders/lambda/" class="list-group-item">Lambda Builder</a> <a href="/{{NAME}}/deployment/builders/lambda/" class="list-group-item">Lambda Builder</a>
<a href="/{{NAME}}/deployment/builders/nixpacks/" class="list-group-item">Nixpacks Builder</a> <a href="/{{NAME}}/deployment/builders/nixpacks/" class="list-group-item">Nixpacks Builder</a>
<a href="/{{NAME}}/deployment/builders/null/" class="list-group-item">Null Builder</a> <a href="/{{NAME}}/deployment/builders/null/" class="list-group-item">Null Builder</a>
<a href="/{{NAME}}/deployment/builders/railpack/" class="list-group-item">Railpack Builder</a>
<a href="#" class="list-group-item disabled">Deployment Methods</a> <a href="#" class="list-group-item disabled">Deployment Methods</a>
<a href="/{{NAME}}/deployment/methods/git/" class="list-group-item">Git Deployment</a> <a href="/{{NAME}}/deployment/methods/git/" class="list-group-item">Git Deployment</a>

21
dokku
View File

@@ -135,6 +135,27 @@ execute_dokku_cmd() {
local script local script
local argv=("$@") local argv=("$@")
local -A PLUGIN_ALIASES
PLUGIN_ALIASES["docker-local"]="scheduler-docker-local"
PLUGIN_ALIASES["dockerfile"]="builder-dockerfile"
PLUGIN_ALIASES["herokuish"]="builder-herokuish"
PLUGIN_ALIASES["k3s"]="scheduler-k3s"
PLUGIN_ALIASES["lambda"]="builder-lambda"
PLUGIN_ALIASES["nixpacks"]="builder-nixpacks"
PLUGIN_ALIASES["pack"]="builder-pack"
PLUGIN_ALIASES["rails"]="builder-railpack"
local plugin_alias_source="${PLUGIN_NAME%%:*}"
local plugin_alias_target="${PLUGIN_ALIASES[$plugin_alias_source]}"
if [[ -n "$plugin_alias_target" ]]; then
PLUGIN_NAME="${PLUGIN_NAME/$plugin_alias_source/$plugin_alias_target}"
PLUGIN_CMD="${PLUGIN_CMD/$plugin_alias_source/$plugin_alias_target}"
# also replace the first argument in $@ with the new PLUGIN_NAME
shift 1
set -- "$PLUGIN_NAME" "$@"
argv=("$PLUGIN_NAME" "${argv[@]:1}")
fi
case "$PLUGIN_NAME" in case "$PLUGIN_NAME" in
events | events:*) events | events:*)
local PLUGIN_NAME=${PLUGIN_NAME/events/20_events} local PLUGIN_NAME=${PLUGIN_NAME/events/20_events}

View File

@@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/dokku/dokku/plugins/common" "github.com/dokku/dokku/plugins/common"
"github.com/tailscale/hujson"
"k8s.io/utils/ptr" "k8s.io/utils/ptr"
) )
@@ -55,8 +56,14 @@ type CronTask struct {
// Command is the command to execute // Command is the command to execute
Command string `json:"command"` Command string `json:"command"`
// Maintenance is whether or not the cron task is in maintenance mode
Maintenance bool `json:"maintenance"`
// Schedule is the cron schedule to execute the command on // Schedule is the cron schedule to execute the command on
Schedule string `json:"schedule"` Schedule string `json:"schedule"`
// ConcurrencyPolicy is the concurrency policy for the cron command
ConcurrencyPolicy string `json:"concurrency_policy"`
} }
// Formation is a struct that represents the scale for a process from an app.json file // Formation is a struct that represents the scale for a process from an app.json file
@@ -256,8 +263,14 @@ func ReadAppJSON(path string) (AppJSON, error) {
return AppJSON{}, nil return AppJSON{}, nil
} }
ast, err := hujson.Parse(b)
if err != nil {
return AppJSON{}, fmt.Errorf("Cannot parse app.json as jsonc: %v", err)
}
ast.Standardize()
var appJSON AppJSON var appJSON AppJSON
if err = json.Unmarshal(b, &appJSON); err != nil { if err = json.Unmarshal(ast.Pack(), &appJSON); err != nil {
return AppJSON{}, fmt.Errorf("Cannot parse app.json: %v", err) return AppJSON{}, fmt.Errorf("Cannot parse app.json: %v", err)
} }

View File

@@ -133,11 +133,7 @@ func cleanupDeploymentContainer(containerID string, phase string) error {
} }
func executeScript(appName string, image string, imageTag string, phase string) error { func executeScript(appName string, image string, imageTag string, phase string) error {
phaseName := phase common.LogInfo1(fmt.Sprintf("Checking for %s task", phase))
if phase == "heroku.postdeploy" {
phaseName = "first deploy postdeploy"
}
common.LogInfo1(fmt.Sprintf("Checking for %s task", phaseName))
command := "" command := ""
phaseSource := "" phaseSource := ""
if phase == "release" { if phase == "release" {
@@ -152,14 +148,14 @@ func executeScript(appName string, image string, imageTag string, phase string)
} }
if command == "" { if command == "" {
common.LogVerbose(fmt.Sprintf("No %s task found, skipping", phaseName)) common.LogVerbose(fmt.Sprintf("No %s task found, skipping", phase))
return nil return nil
} }
if phase == "predeploy" { if phase == "predeploy" {
common.LogVerbose(fmt.Sprintf("Executing %s task from %s: %s", phaseName, phaseSource, command)) common.LogVerbose(fmt.Sprintf("Executing %s task from %s: %s", phase, phaseSource, command))
} else { } else {
common.LogVerbose(fmt.Sprintf("Executing %s task from %s in ephemeral container: %s", phaseName, phaseSource, command)) common.LogVerbose(fmt.Sprintf("Executing %s task from %s in ephemeral container: %s", phase, phaseSource, command))
} }
isHerokuishImage := common.IsImageHerokuishBased(image, appName) isHerokuishImage := common.IsImageHerokuishBased(image, appName)
@@ -271,15 +267,15 @@ func executeScript(appName string, image string, imageTag string, phase string)
defer cleanupDeploymentContainer(containerID, phase) defer cleanupDeploymentContainer(containerID, phase)
if !waitForExecution(containerID) { if !waitForExecution(containerID) {
common.LogInfo2Quiet(fmt.Sprintf("Start of %s %s task (%s) output", appName, phaseName, containerID[0:9])) common.LogInfo2Quiet(fmt.Sprintf("Start of %s %s task (%s) output", appName, phase, containerID[0:9]))
common.LogVerboseQuietContainerLogs(containerID) common.LogVerboseQuietContainerLogs(containerID)
common.LogInfo2Quiet(fmt.Sprintf("End of %s %s task (%s) output", appName, phaseName, containerID[0:9])) common.LogInfo2Quiet(fmt.Sprintf("End of %s %s task (%s) output", appName, phase, containerID[0:9]))
return fmt.Errorf("Execution of %s task failed: %s", phaseName, command) return fmt.Errorf("Execution of %s task failed: %s", phase, command)
} }
common.LogInfo2Quiet(fmt.Sprintf("Start of %s %s task (%s) output", appName, phaseName, containerID[0:9])) common.LogInfo2Quiet(fmt.Sprintf("Start of %s %s task (%s) output", appName, phase, containerID[0:9]))
common.LogVerboseQuietContainerLogs(containerID) common.LogVerboseQuietContainerLogs(containerID)
common.LogInfo2Quiet(fmt.Sprintf("End of %s %s task (%s) output", appName, phaseName, containerID[0:9])) common.LogInfo2Quiet(fmt.Sprintf("End of %s %s task (%s) output", appName, phase, containerID[0:9]))
if phase != "predeploy" { if phase != "predeploy" {
return nil return nil

View File

@@ -6,6 +6,7 @@ require (
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.10
github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a
k8s.io/utils v0.0.0-20240102154912-e7106e64919e k8s.io/utils v0.0.0-20240102154912-e7106e64919e
) )
@@ -23,6 +24,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/sftp v1.13.5 // indirect github.com/pkg/sftp v1.13.5 // indirect
github.com/ryanuber/columnize v2.1.2+incompatible // indirect github.com/ryanuber/columnize v2.1.2+incompatible // indirect
github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/crypto v0.43.0 // indirect golang.org/x/crypto v0.43.0 // indirect
golang.org/x/sync v0.17.0 // indirect golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect golang.org/x/sys v0.37.0 // indirect

View File

@@ -39,8 +39,11 @@ github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a h1:a6TNDN9CgG+cYjaeN8l2mc4kSz2iMiCDQxPEyltUV/I=
github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=

View File

@@ -1,9 +1,9 @@
package appjson package appjson
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/dokku/dokku/plugins/common" "github.com/dokku/dokku/plugins/common"
@@ -42,18 +42,17 @@ func TriggerAppJSONGetContent(appName string) error {
return nil return nil
} }
b, err := os.ReadFile(getProcessSpecificAppJSONPath(appName)) appJSON, err := ReadAppJSON(getProcessSpecificAppJSONPath(appName))
if err != nil { if err != nil {
return fmt.Errorf("Cannot read app.json file: %v", err) return err
} }
content := strings.TrimSpace(string(b)) content, err := json.Marshal(appJSON)
if content == "" { if err != nil {
fmt.Print("{}") return err
return nil
} }
fmt.Print(content) fmt.Print(string(content))
return nil return nil
} }

View File

@@ -17,8 +17,6 @@ trigger-builder-dockerfile-builder-build() {
dokku_log_info1 "Building $APP from Dockerfile" dokku_log_info1 "Building $APP from Dockerfile"
local IMAGE=$(get_app_image_name "$APP") local IMAGE=$(get_app_image_name "$APP")
local DOKKU_DOCKERFILE_CACHE_BUILD=$(config_get "$APP" "DOKKU_DOCKERFILE_CACHE_BUILD")
local DOKKU_DOCKER_BUILD_OPTS=$(config_get "$APP" "DOKKU_DOCKER_BUILD_OPTS")
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=dockerfile" "--label=com.dokku.app-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=dockerfile" "--label=com.dokku.app-name=$APP")
pushd "$SOURCECODE_WORK_DIR" &>/dev/null pushd "$SOURCECODE_WORK_DIR" &>/dev/null
@@ -34,17 +32,221 @@ trigger-builder-dockerfile-builder-build() {
fi fi
plugn trigger pre-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR" plugn trigger pre-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR"
[[ "$DOKKU_DOCKERFILE_CACHE_BUILD" == "false" ]] && DOKKU_DOCKER_BUILD_OPTS="$DOKKU_DOCKER_BUILD_OPTS --no-cache"
local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$BUILDER_TYPE") local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$BUILDER_TYPE")
DOCKER_ARGS+=$(: | plugn trigger docker-args-process-build "$APP" "$BUILDER_TYPE") DOCKER_ARGS+=$(: | plugn trigger docker-args-process-build "$APP" "$BUILDER_TYPE")
DOCKER_ARGS+=" $DOKKU_GLOBAL_BUILD_ARGS"
# strip --link, --volume and -v args from DOCKER_ARGS DOCKER_ARGS=" $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") eval set -- "$DOCKER_ARGS"
declare -a ARG_ARRAY
eval "ARG_ARRAY=($DOCKER_ARGS)" declare -a DOCKERFILE_ARGS
while true; do
case "$1" in
--add-host)
DOCKERFILE_ARGS+=("--add-host")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--add-host=*)
DOCKERFILE_ARGS+=("--add-host" "${1#--add-host=}")
shift 1
;;
--allow)
DOCKERFILE_ARGS+=("--allow")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--allow=*)
DOCKERFILE_ARGS+=("--allow" "${1#--allow=}")
shift 1
;;
--annotation)
DOCKERFILE_ARGS+=("--annotation")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--annotation=*)
DOCKERFILE_ARGS+=("--annotation" "${1#--annotation=}")
shift 1
;;
--attest)
DOCKERFILE_ARGS+=("--attest")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--attest=*)
DOCKERFILE_ARGS+=("--attest" "${1#--attest=}")
shift 1
;;
--build-arg)
DOCKERFILE_ARGS+=("--build-arg")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--build-arg=*)
DOCKERFILE_ARGS+=("--build-arg" "${1#--build-arg=}")
shift 1
;;
--builder)
DOCKERFILE_ARGS+=("--builder")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--builder=*)
DOCKERFILE_ARGS+=("--builder" "${1#--builder=}")
shift 1
;;
--cache-from)
DOCKERFILE_ARGS+=("--cache-from")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--cache-from=*)
DOCKERFILE_ARGS+=("--cache-from" "${1#--cache-from=}")
shift 1
;;
--cache-to)
DOCKERFILE_ARGS+=("--cache-to")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--cache-to=*)
DOCKERFILE_ARGS+=("--cache-to" "${1#--cache-to=}")
shift 1
;;
--call)
DOCKERFILE_ARGS+=("--call")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--call=*)
DOCKERFILE_ARGS+=("--call" "${1#--call=}")
shift 1
;;
--cgroup-parent)
DOCKERFILE_ARGS+=("--cgroup-parent")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--cgroup-parent=*)
DOCKERFILE_ARGS+=("--cgroup-parent" "${1#--cgroup-parent=}")
shift 1
;;
--label)
DOCKERFILE_ARGS+=("--label")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--label=*)
DOCKERFILE_ARGS+=("--label" "${1#--label=}")
shift 1
;;
--network)
DOCKERFILE_ARGS+=("--network")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--network=*)
DOCKERFILE_ARGS+=("--network" "${1#--network=}")
shift 1
;;
--platform)
DOCKERFILE_ARGS+=("--platform")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--platform=*)
DOCKERFILE_ARGS+=("--platform" "${1#--platform=}")
shift 1
;;
--progress)
DOCKERFILE_ARGS+=("--progress")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--progress=*)
DOCKERFILE_ARGS+=("--progress" "${1#--progress=}")
shift 1
;;
--provenance)
DOCKERFILE_ARGS+=("--provenance")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--provenance=*)
DOCKERFILE_ARGS+=("--provenance" "${1#--provenance=}")
shift 1
;;
--sbom)
DOCKERFILE_ARGS+=("--sbom")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--sbom=*)
DOCKERFILE_ARGS+=("--sbom" "${1#--sbom=}")
shift 1
;;
--secret)
DOCKERFILE_ARGS+=("--secret")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--secret=*)
DOCKERFILE_ARGS+=("--secret" "${1#--secret=}")
shift 1
;;
--shm-size)
DOCKERFILE_ARGS+=("--shm-size")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--shm-size=*)
DOCKERFILE_ARGS+=("--shm-size" "${1#--shm-size=}")
shift 1
;;
--ssh)
DOCKERFILE_ARGS+=("--platform")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--ssh=*)
DOCKERFILE_ARGS+=("--ssh" "${1#--ssh=}")
shift 1
;;
--target)
DOCKERFILE_ARGS+=("--target")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--target=*)
DOCKERFILE_ARGS+=("--target" "${1#--target=}")
shift 1
;;
--ulimit)
DOCKERFILE_ARGS+=("--tag")
DOCKERFILE_ARGS+=("$2")
shift 2
;;
--tag=*)
DOCKERFILE_ARGS+=("--tag" "${1#--tag=}")
shift 1
;;
--check | -D | --debug | --no-cache)
DOCKERFILE_ARGS+=("$1")
shift 1
;;
*)
if [[ -n "$1" ]]; then
shift
continue
fi
break
;;
esac
done
eval "$(config_export app "$APP")" eval "$(config_export app "$APP")"
"$DOCKER_BIN" image build "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS "${ARG_ARRAY[@]}" ${DOKKU_DOCKER_BUILD_OPTS} -t $IMAGE . "$DOCKER_BIN" image build "${DOCKER_BUILD_LABEL_ARGS[@]}" "${DOCKERFILE_ARGS[@]}" --tag $IMAGE .
plugn trigger ports-set-detected "$APP" "$(fn-builder-dockerfile-get-detect-port-map "$APP" "$IMAGE" "$SOURCECODE_WORK_DIR/Dockerfile")" plugn trigger ports-set-detected "$APP" "$(fn-builder-dockerfile-get-detect-port-map "$APP" "$IMAGE" "$SOURCECODE_WORK_DIR/Dockerfile")"
if fn-plugn-trigger-exists "post-build-dockerfile"; then if fn-plugn-trigger-exists "post-build-dockerfile"; then

View File

@@ -61,7 +61,10 @@ trigger-builder-herokuish-builder-build() {
plugn trigger pre-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR" plugn trigger pre-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR"
local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$BUILDER_TYPE") local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$BUILDER_TYPE")
[[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" --env=TRACE=true " if [[ -n "$DOKKU_TRACE" ]]; then
DOCKER_ARGS+=" --env=TRACE=true "
DOCKER_ARGS+=" --env=BUILDPACK_XTRACE=1 "
fi
DOCKER_ARGS+=$(: | plugn trigger docker-args-process-build "$APP" "$BUILDER_TYPE") DOCKER_ARGS+=$(: | plugn trigger docker-args-process-build "$APP" "$BUILDER_TYPE")
declare -a ARG_ARRAY declare -a ARG_ARRAY

View File

@@ -27,30 +27,224 @@ trigger-builder-nixpacks-builder-build() {
plugn trigger pre-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR" plugn trigger pre-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR"
no_cache="$(fn-builder-nixpacks-computed-no-cache "$APP")"
NIXPACKS_ARGS=""
if [[ "$no_cache" == "true" ]]; then
NIXPACKS_ARGS="$NIXPACKS_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 [[ -f "$SOURCECODE_WORK_DIR/Procfile" ]]; then
if procfile-util exists --process-type release; then if procfile-util exists --process-type release; then
procfile-util delete --process-type release procfile-util delete --process-type release
fi fi
fi fi
local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$BUILDER_TYPE")
DOCKER_ARGS+=$(: | plugn trigger docker-args-process-build "$APP" "$BUILDER_TYPE")
DOCKER_ARGS+=" $DOKKU_GLOBAL_BUILD_ARGS"
DOCKER_ARGS=" $DOCKER_ARGS "
eval set -- "$DOCKER_ARGS"
declare -a NIXPACKS_ARGS
while true; do
case "$1" in
-t | --tag)
NIXPACKS_ARGS+=("--tag")
NIXPACKS_ARGS+=("$2")
shift 2
;;
-t=* | --tag=*)
if [[ "$1" == "--tag=*" ]]; then
NIXPACKS_ARGS+=("--tag" "${1#--tag=}")
elif [[ "$1" == "-t=*" ]]; then
NIXPACKS_ARGS+=("--tag" "${1#-t=}")
fi
shift 1
;;
-i | --install-cmd)
NIXPACKS_ARGS+=("--install-cmd")
NIXPACKS_ARGS+=("$2")
shift 2
;;
-i=* | --install-cmd=*)
if [[ "$1" == "--install-cmd=*" ]]; then
NIXPACKS_ARGS+=("--install-cmd" "${1#--install-cmd=}")
elif [[ "$1" == "-i=*" ]]; then
NIXPACKS_ARGS+=("--install-cmd" "${1#-i=}")
fi
shift 1
;;
-l | --label)
NIXPACKS_ARGS+=("--label")
NIXPACKS_ARGS+=("$2")
shift 2
;;
-l=* | --label=*)
if [[ "$1" == "--label=*" ]]; then
NIXPACKS_ARGS+=("--label" "${1#--label=}")
elif [[ "$1" == "-l=*" ]]; then
NIXPACKS_ARGS+=("--label" "${1#-l=}")
fi
shift 1
;;
-b | --build-cmd)
NIXPACKS_ARGS+=("--build-cmd")
NIXPACKS_ARGS+=("$2")
shift 2
;;
-b=* | --build-cmd=*)
if [[ "$1" == "--build-cmd=*" ]]; then
NIXPACKS_ARGS+=("--build-cmd" "${1#--build-cmd=}")
elif [[ "$1" == "-b=*" ]]; then
NIXPACKS_ARGS+=("--build-cmd" "${1#-b=}")
fi
shift 1
;;
-s | --start-cmd)
NIXPACKS_ARGS+=("--start-cmd")
NIXPACKS_ARGS+=("$2")
shift 2
;;
-s=* | --start-cmd=*)
if [[ "$1" == "--start-cmd=*" ]]; then
NIXPACKS_ARGS+=("--start-cmd" "${1#--start-cmd=}")
elif [[ "$1" == "-s=*" ]]; then
NIXPACKS_ARGS+=("--start-cmd" "${1#-s=}")
fi
shift 1
;;
-p | --pkgs)
NIXPACKS_ARGS+=("--pkgs")
NIXPACKS_ARGS+=("$2")
shift 2
;;
-p=* | --pkgs=*)
if [[ "$1" == "--pkgs=*" ]]; then
NIXPACKS_ARGS+=("--pkgs" "${1#--pkgs=}")
elif [[ "$1" == "-p=*" ]]; then
NIXPACKS_ARGS+=("--pkgs" "${1#-p=}")
fi
shift 1
;;
-a | --apt)
NIXPACKS_ARGS+=("--apt")
NIXPACKS_ARGS+=("$2")
shift 2
;;
-a=* | --apt=*)
if [[ "$1" == "--apt=*" ]]; then
NIXPACKS_ARGS+=("--apt" "${1#--apt=}")
elif [[ "$1" == "-a=*" ]]; then
NIXPACKS_ARGS+=("--apt" "${1#-a=}")
fi
shift 1
;;
-e | --env)
NIXPACKS_ARGS+=("--env")
NIXPACKS_ARGS+=("$2")
shift 2
;;
-e=* | --env=*)
if [[ "$1" == "--env=*" ]]; then
NIXPACKS_ARGS+=("--env" "${1#--env=}")
elif [[ "$1" == "-e=*" ]]; then
NIXPACKS_ARGS+=("--env" "${1#-e=}")
fi
shift 1
;;
--platform)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--platform=*)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--cache-key)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--cache-key=*)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--incremental-cache-image)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--incremental-cache-image=*)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--libs)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--libs=*)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--cache-from)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--cache-from=*)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--docker-host)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--docker-host=*)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--add-host)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--add-host=*)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--docker-tls-verify)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--docker-tls-verify=*)
NIXPACKS_ARGS+=("$1")
NIXPACKS_ARGS+=("$2")
shift 2
;;
--no-cache | --inline-cache | --no-error-without-start)
NIXPACKS_ARGS+=("$1")
shift 1
;;
*)
if [[ -n "$1" ]]; then
shift
continue
fi
break
;;
esac
done
eval "$(config_export app "$APP" --merged)"
# shellcheck disable=SC2086 # shellcheck disable=SC2086
if ! nixpacks build "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS "${ARG_ARRAY[@]}" $NIXPACKS_ARGS --name "$IMAGE" "$SOURCECODE_WORK_DIR"; then if ! nixpacks build "${DOCKER_BUILD_LABEL_ARGS[@]}" "${NIXPACKS_ARGS[@]}" --name "$IMAGE" "$SOURCECODE_WORK_DIR"; then
dokku_log_warn "Failure building image" dokku_log_warn "Failure building image"
return 1 return 1
fi fi

View File

@@ -38,9 +38,6 @@ cmd-builder-nixpacks-report-single() {
"--builder-nixpacks-computed-nixpackstoml-path: $(fn-builder-nixpacks-computed-nixpackstoml-path "$APP")" "--builder-nixpacks-computed-nixpackstoml-path: $(fn-builder-nixpacks-computed-nixpackstoml-path "$APP")"
"--builder-nixpacks-global-nixpackstoml-path: $(fn-builder-nixpacks-global-nixpackstoml-path "$APP")" "--builder-nixpacks-global-nixpackstoml-path: $(fn-builder-nixpacks-global-nixpackstoml-path "$APP")"
"--builder-nixpacks-nixpackstoml-path: $(fn-builder-nixpacks-nixpackstoml-path "$APP")" "--builder-nixpacks-nixpackstoml-path: $(fn-builder-nixpacks-nixpackstoml-path "$APP")"
"--builder-nixpacks-computed-no-cache: $(fn-builder-nixpacks-computed-no-cache "$APP")"
"--builder-nixpacks-global-no-cache: $(fn-builder-nixpacks-global-no-cache "$APP")"
"--builder-nixpacks-no-cache: $(fn-builder-nixpacks-no-cache "$APP")"
) )
if [[ -z "$INFO_FLAG" ]]; then if [[ -z "$INFO_FLAG" ]]; then
@@ -91,26 +88,3 @@ fn-builder-nixpacks-nixpackstoml-path() {
fn-plugin-property-get "builder-nixpacks" "$APP" "nixpackstoml-path" "" fn-plugin-property-get "builder-nixpacks" "$APP" "nixpackstoml-path" ""
} }
fn-builder-nixpacks-computed-no-cache() {
declare APP="$1"
file="$(fn-builder-nixpacks-no-cache "$APP")"
if [[ "$file" == "" ]]; then
file="$(fn-builder-nixpacks-global-no-cache "$APP")"
fi
echo "$file"
}
fn-builder-nixpacks-global-no-cache() {
declare APP="$1"
fn-plugin-property-get "builder-nixpacks" "--global" "no-cache" "false"
}
fn-builder-nixpacks-no-cache() {
declare APP="$1"
fn-plugin-property-get "builder-nixpacks" "$APP" "no-cache" ""
}

View File

@@ -9,13 +9,13 @@ cmd-builder-nixpacks-set() {
declare cmd="builder-nixpacks:set" declare cmd="builder-nixpacks:set"
[[ "$1" == "$cmd" ]] && shift 1 [[ "$1" == "$cmd" ]] && shift 1
declare APP="$1" KEY="$2" VALUE="$3" declare APP="$1" KEY="$2" VALUE="$3"
local VALID_KEYS=("nixpackstoml-path" "no-cache") local VALID_KEYS=("nixpackstoml-path")
[[ "$APP" == "--global" ]] || verify_app_name "$APP" [[ "$APP" == "--global" ]] || verify_app_name "$APP"
[[ -z "$KEY" ]] && dokku_log_fail "No key specified" [[ -z "$KEY" ]] && dokku_log_fail "No key specified"
if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then
dokku_log_fail "Invalid key specified, valid keys include: nixpackstoml-path, no-cache" dokku_log_fail "Invalid key specified, valid keys include: nixpackstoml-path"
fi fi
if [[ -n "$VALUE" ]]; then if [[ -n "$VALUE" ]]; then

View File

@@ -38,33 +38,261 @@ trigger-builder-pack-builder-build() {
plugn trigger pre-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR" plugn trigger pre-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR"
local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$BUILDER_TYPE") local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$BUILDER_TYPE")
[[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" --env=TRACE=true "
DOCKER_ARGS+=$(: | plugn trigger docker-args-process-build "$APP" "$BUILDER_TYPE") DOCKER_ARGS+=$(: | plugn trigger docker-args-process-build "$APP" "$BUILDER_TYPE")
[[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" --env=TRACE=true "
DOCKER_ARGS=" $DOCKER_ARGS " DOCKER_ARGS=" $DOCKER_ARGS "
declare -a ARG_ARRAY
eval "ARG_ARRAY=($DOCKER_ARGS)"
eval set -- "$DOCKER_ARGS" eval set -- "$DOCKER_ARGS"
declare -a PACK_ARGS declare -a PACK_ARGS
while true; do while true; do
case "$1" in case "$1" in
--volume=* | -v=*) -b | --buildpack)
PACK_ARGS+=("$1") PACK_ARGS+=("--buildpack")
shift PACK_ARGS+=("$2")
shift 2
;;
-b=* | --buildpack=*)
if [[ "$1" == "--buildpack=*" ]]; then
PACK_ARGS+=("--buildpack" "${1#--buildpack=}")
elif [[ "$1" == "-b=*" ]]; then
PACK_ARGS+=("--buildpack" "${1#-b=}")
fi
shift 1
;;
-r | --buildpack-registry)
PACK_ARGS+=("--buildpack-registry")
PACK_ARGS+=("$2")
shift 2
;;
-r=* | --buildpack-registry=*)
if [[ "$1" == "--buildpack-registry=*" ]]; then
PACK_ARGS+=("--buildpack-registry" "${1#--buildpack-registry=}")
elif [[ "$1" == "-r=*" ]]; then
PACK_ARGS+=("--buildpack-registry" "${1#-r=}")
fi
shift 2
;;
-e | --env)
PACK_ARGS+=("--env")
PACK_ARGS+=("$2")
shift 2
;;
-e=* | --env=*)
if [[ "$1" == "--env=*" ]]; then
PACK_ARGS+=("--env" "${1#--env=}")
elif [[ "$1" == "-e=*" ]]; then
PACK_ARGS+=("--env" "${1#-e=}")
fi
shift 1
;;
-t | --tag)
PACK_ARGS+=("--tag")
PACK_ARGS+=("$2")
shift 2
;;
-t=* | --tag=*)
if [[ "$1" == "--tag=*" ]]; then
PACK_ARGS+=("--tag" "${1#--tag=}")
elif [[ "$1" == "-t=*" ]]; then
PACK_ARGS+=("--tag" "${1#-t=}")
fi
shift 1
;; ;;
--volume | -v) --volume | -v)
PACK_ARGS+=("--volume") PACK_ARGS+=("--volume")
PACK_ARGS+=("$2") PACK_ARGS+=("$2")
shift 2 shift 2
;; ;;
--volume=* | -v=*)
if [[ "$1" == "--volume=*" ]]; then
PACK_ARGS+=("--volume" "${1#--volume=}")
elif [[ "$1" == "-v=*" ]]; then
PACK_ARGS+=("--volume" "${1#-v=}")
fi
shift 1
;;
--cache)
PACK_ARGS+=("--cache")
PACK_ARGS+=("$2")
shift 2
;;
--cache=*)
PACK_ARGS+=("--cache" "${1#--cache=}")
shift 1
;;
--cache-image)
PACK_ARGS+=("--cache-image")
PACK_ARGS+=("$2")
shift 2
;;
--cache-image=*)
PACK_ARGS+=("--cache-image" "${1#--cache-image=}")
shift 1
;;
--creation-time)
PACK_ARGS+=("--creation-time")
PACK_ARGS+=("$2")
shift 2
;;
--creation-time=*)
PACK_ARGS+=("--creation-time" "${1#--creation-time=}")
shift 1
;;
--env-file)
PACK_ARGS+=("--env-file")
PACK_ARGS+=("$2")
shift 2
;;
--env-file=*)
PACK_ARGS+=("--env-file" "${1#--env-file=}")
shift 1
;;
--extension)
PACK_ARGS+=("--extension")
PACK_ARGS+=("$2")
shift 2
;;
--extension=*)
PACK_ARGS+=("--extension" "${1#--extension=}")
shift 1
;;
--gid)
PACK_ARGS+=("--gid")
PACK_ARGS+=("$2")
shift 2
;;
--gid=*)
PACK_ARGS+=("--gid" "${1#--gid=}")
shift 1
;;
--insecure-registry)
PACK_ARGS+=("--insecure-registry")
PACK_ARGS+=("$2")
shift 2
;;
--insecure-registry=*)
PACK_ARGS+=("--insecure-registry" "${1#--insecure-registry=}")
shift 1
;;
--lifecycle-image)
PACK_ARGS+=("--lifecycle-image")
PACK_ARGS+=("$2")
shift 2
;;
--lifecycle-image=*)
PACK_ARGS+=("--lifecycle-image" "${1#--lifecycle-image=}")
shift 1
;;
--network)
PACK_ARGS+=("--network")
PACK_ARGS+=("$2")
shift 2
;;
--network=*)
PACK_ARGS+=("--network" "${1#--network=}")
shift 1
;;
--platform)
PACK_ARGS+=("--platform")
PACK_ARGS+=("$2")
shift 2
;;
--platform=*)
PACK_ARGS+=("--platform" "${1#--platform=}")
shift 1
;;
--post-buildpack)
PACK_ARGS+=("--post-buildpack")
PACK_ARGS+=("$2")
shift 2
;;
--post-buildpack=*)
PACK_ARGS+=("--post-buildpack" "${1#--post-buildpack=}")
shift 1
;;
--pre-buildpack)
PACK_ARGS+=("--pre-buildpack")
PACK_ARGS+=("$2")
shift 2
;;
--pre-buildpack=*)
PACK_ARGS+=("--pre-buildpack" "${1#--pre-buildpack=}")
shift 1
;;
--previous-image)
PACK_ARGS+=("--previous-image")
PACK_ARGS+=("$2")
shift 2
;;
--previous-image=*)
PACK_ARGS+=("--previous-image" "${1#--previous-image=}")
shift 1
;;
--pull-policy)
PACK_ARGS+=("--pull-policy")
PACK_ARGS+=("$2")
shift 2
;;
--pull-policy=*)
PACK_ARGS+=("--pull-policy" "${1#--pull-policy=}")
shift 1
;;
--run-image)
PACK_ARGS+=("--run-image")
PACK_ARGS+=("$2")
shift 2
;;
--run-image=*)
PACK_ARGS+=("--run-image" "${1#--run-image=}")
shift 1
;;
--uid)
PACK_ARGS+=("--uid")
PACK_ARGS+=("$2")
shift 2
;;
--uid=*)
PACK_ARGS+=("--uid" "${1#--uid=}")
shift 1
;;
--workspace)
PACK_ARGS+=("--workspace")
PACK_ARGS+=("$2")
shift 2
;;
--workspace=*)
PACK_ARGS+=("--workspace" "${1#--workspace=}")
shift 1
;;
--clear-cache | --disable-system-buildpacks | --publish | --trust-builder | --trust-extra-buildpacks | --userns-host)
PACK_ARGS+=("$1")
shift 1
;;
*) *)
if [[ -n "$1" ]]; then
shift
continue
fi
break break
;; ;;
esac esac
done done
if [ -n "$BUILDPACK_URL" ]; then
PACK_ARGS+=("--builder")
PACK_ARGS+=("$BUILDPACK_URL")
elif [ -f "$SOURCECODE_WORK_DIR/.buildpacks" ]; then
while read -r buildpack; do
if [ -z "$buildpack" ]; then
continue
fi
PACK_ARGS+=("--builder")
PACK_ARGS+=("$buildpack")
done <"$SOURCECODE_WORK_DIR/.buildpacks"
fi
pack build "$IMAGE" --builder "$DOKKU_CNB_BUILDER" --path "$SOURCECODE_WORK_DIR" --default-process web "${PACK_ARGS[@]}" "${ENV_ARGS[@]}" pack build "$IMAGE" --builder "$DOKKU_CNB_BUILDER" --path "$SOURCECODE_WORK_DIR" --default-process web "${PACK_ARGS[@]}" "${ENV_ARGS[@]}"
docker-image-labeler relabel --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=pack --label=com.dokku.app-name=$APP "$IMAGE" docker-image-labeler relabel --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=pack --label=com.dokku.app-name=$APP "$IMAGE"

View File

@@ -12,11 +12,6 @@ trigger-builder-pack-builder-detect() {
echo "pack" echo "pack"
return return
fi fi
if [[ "$(config_get "$APP" DOKKU_CNB_EXPERIMENTAL || true)" == "1" ]]; then
echo "pack"
return
fi
} }
trigger-builder-pack-builder-detect "$@" trigger-builder-pack-builder-detect "$@"

View File

@@ -0,0 +1,146 @@
#!/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"
if [[ -f "$SOURCECODE_WORK_DIR/Procfile" ]]; then
if procfile-util exists --process-type release; then
procfile-util delete --process-type release
fi
fi
local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$BUILDER_TYPE")
DOCKER_ARGS+=$(: | plugn trigger docker-args-process-build "$APP" "$BUILDER_TYPE")
DOCKER_ARGS=" $DOCKER_ARGS "
eval set -- "$DOCKER_ARGS"
declare -a RAILPACK_ARGS
while true; do
case "$1" in
--platform)
RAILPACK_ARGS+=("--platform")
RAILPACK_ARGS+=("$2")
shift 2
;;
--platform=*)
RAILPACK_ARGS+=("--platform" "${1#--platform=}")
shift 1
;;
--progress)
RAILPACK_ARGS+=("--progress")
RAILPACK_ARGS+=("$2")
shift 2
;;
--progress=*)
RAILPACK_ARGS+=("--progress" "${1#--progress=}")
shift 1
;;
--cache-key)
RAILPACK_ARGS+=("--cache-key")
RAILPACK_ARGS+=("$2")
shift 2
;;
--cache-key=*)
RAILPACK_ARGS+=("--cache-key" "${1#--cache-key=}")
shift 1
;;
-e | --env)
RAILPACK_ARGS+=("--env")
RAILPACK_ARGS+=("$2")
shift 2
;;
-e=* | --env=*)
if [[ "$1" == "--env=*" ]]; then
RAILPACK_ARGS+=("--env" "${1#--env=}")
elif [[ "$1" == "--e=*" ]]; then
RAILPACK_ARGS+=("--env" "${1#-e=}")
fi
shift 1
;;
--previous)
RAILPACK_ARGS+=("--previous")
RAILPACK_ARGS+=("$2")
shift 2
;;
--previous=*)
RAILPACK_ARGS+=("--previous" "${1#--previous=}")
shift 1
;;
--build-cmd)
RAILPACK_ARGS+=("--build-cmd")
RAILPACK_ARGS+=("$2")
shift 2
;;
--build-cmd=*)
RAILPACK_ARGS+=("--build-cmd" "${1#--build-cmd=}")
shift 1
;;
--start-cmd)
RAILPACK_ARGS+=("--start-cmd")
RAILPACK_ARGS+=("$2")
shift 2
;;
--start-cmd=*)
RAILPACK_ARGS+=("--start-cmd" "${1#--start-cmd=}")
shift 1
;;
---show-plan | --error-missing-start)
RAILPACK_ARGS+=("$1")
shift 1
;;
*)
if [[ -n "$1" ]]; then
shift
continue
fi
break
;;
esac
done
eval "$(config_export app "$APP" --merged)"
# shellcheck disable=SC2086
if ! railpack build "${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 "$@"

View File

@@ -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 "$@"

View File

@@ -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 "$@"

View File

@@ -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

View File

@@ -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 "$@"

View File

@@ -0,0 +1,4 @@
ARG APP_IMAGE
FROM $APP_IMAGE
ENTRYPOINT []

View File

@@ -0,0 +1,2 @@
ARG APP_IMAGE
FROM $APP_IMAGE

View File

@@ -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 <<help_desc
$plugin_name, $plugin_description
help_desc
fi
}
fn-help-content() {
declare desc="return help content"
cat <<help_content
builder-railpack:report [<app>] [<flag>], Displays a builder-railpack report for one or more apps
builder-railpack:set <app> <property> (<value>), Set or clear a builder-railpack property for an app
help_content
}

View File

@@ -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 "$@"

View File

@@ -0,0 +1,90 @@
#!/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")"
)
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" ""
}

View File

@@ -0,0 +1,4 @@
[plugin]
description = "dokku core builder-railpack plugin"
version = "0.36.7"
[plugin.config]

View File

@@ -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 "$@"

View File

@@ -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 "$@"

View File

@@ -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 "$@"

View File

@@ -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 "$@"

View File

@@ -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"

View File

@@ -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 "$@"

View File

@@ -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")
[[ "$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"
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 "$@"

View File

@@ -1,4 +1,4 @@
SUBCOMMANDS = subcommands/add subcommands/clear subcommands/list subcommands/remove subcommands/report subcommands/set subcommands/set-property SUBCOMMANDS = subcommands/add subcommands/clear subcommands/detect subcommands/list subcommands/remove subcommands/report subcommands/set subcommands/set-property
TRIGGERS = triggers/buildpack-stack-name triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/post-extract triggers/report TRIGGERS = triggers/buildpack-stack-name triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/post-extract triggers/report
BUILD = commands subcommands triggers BUILD = commands subcommands triggers
PLUGIN_NAME = buildpacks PLUGIN_NAME = buildpacks

View File

@@ -63,3 +63,32 @@ func validBuildpackURL(buildpack string) (string, error) {
return buildpack, fmt.Errorf("Invalid buildpack specified: %v", buildpack) return buildpack, fmt.Errorf("Invalid buildpack specified: %v", buildpack)
} }
func checkoutBareGitRepo(sourceWorkDir string, branch string) (string, error) {
tmpdir, err := os.MkdirTemp("", "bare-git-checkout")
if err != nil {
return "", fmt.Errorf("Unable to create temporary directory: %s", err.Error())
}
args := []string{
"--git-dir=" + sourceWorkDir,
"--work-tree=" + tmpdir,
"checkout", "-f",
}
if branch != "" {
args = append(args, branch)
}
result, err := common.CallExecCommand(common.ExecCommandInput{
Command: "git",
Args: args,
})
if err != nil {
os.RemoveAll(tmpdir)
return "", fmt.Errorf("Unable to checkout bare git repository: %s", result.StderrContents())
}
return tmpdir, nil
}

View File

@@ -20,6 +20,7 @@ Additional commands:`
helpContent = ` helpContent = `
buildpacks:add [--index 1] <app> <buildpack>, Add new app buildpack while inserting into list of buildpacks if necessary buildpacks:add [--index 1] <app> <buildpack>, Add new app buildpack while inserting into list of buildpacks if necessary
buildpacks:clear <app>, Clear all buildpacks set on the app buildpacks:clear <app>, Clear all buildpacks set on the app
buildpacks:detect <app> [--branch <branch-name>], Detect the buildpack for an app (if not specified, the branch flag defaults to the deploy branch)
buildpacks:list <app>, List all buildpacks for an app buildpacks:list <app>, List all buildpacks for an app
buildpacks:remove <app> <buildpack>, Remove a buildpack set on the app buildpacks:remove <app> <buildpack>, Remove a buildpack set on the app
buildpacks:report [<app>] [<flag>], Displays a buildpack report for one or more apps buildpacks:report [<app>] [<flag>], Displays a buildpack report for one or more apps

View File

@@ -30,6 +30,15 @@ func main() {
args.Parse(os.Args[2:]) args.Parse(os.Args[2:])
appName := args.Arg(0) appName := args.Arg(0)
err = buildpacks.CommandClear(appName) err = buildpacks.CommandClear(appName)
case "detect":
args := flag.NewFlagSet("buildpacks:detect", flag.ExitOnError)
branch := args.String("branch", "", "--branch: the branch to detect against (defaults to the deploy branch)")
args.Parse(os.Args[2:])
appName := args.Arg(0)
if *branch == "" {
*branch = common.PropertyGet("git", appName, "deploy-branch")
}
err = buildpacks.CommandDetect(appName, *branch)
case "list": case "list":
args := flag.NewFlagSet("buildpacks:list", flag.ExitOnError) args := flag.NewFlagSet("buildpacks:list", flag.ExitOnError)
args.Parse(os.Args[2:]) args.Parse(os.Args[2:])

View File

@@ -3,6 +3,7 @@ package buildpacks
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"github.com/dokku/dokku/plugins/common" "github.com/dokku/dokku/plugins/common"
) )
@@ -30,6 +31,41 @@ func CommandClear(appName string) error {
return common.PropertyDelete("buildpacks", appName, "buildpacks") return common.PropertyDelete("buildpacks", appName, "buildpacks")
} }
// CommandDetect implements buildpacks:detect
func CommandDetect(appName string, branch string) (err error) {
if err := common.VerifyAppName(appName); err != nil {
return err
}
workDir := common.AppRoot(appName)
checkedOutDir, err := checkoutBareGitRepo(workDir, branch)
if err != nil {
return err
}
defer func() {
if err := os.RemoveAll(checkedOutDir); err != nil {
common.LogWarn(fmt.Sprintf("Failed to remove temporary directory %s: %v", checkedOutDir, err))
}
}()
dockerArgs := []string{
"run", "--rm",
"-v", fmt.Sprintf("%s:/tmp/app", checkedOutDir),
"gliderlabs/herokuish", "/bin/herokuish", "buildpack", "detect", "/tmp/app",
}
result, err := common.CallExecCommand(common.ExecCommandInput{
Command: common.DockerBin(),
Args: dockerArgs,
})
if err != nil {
return fmt.Errorf("Buildpack detection failed: %s", result.StderrContents())
}
common.LogVerbose(result.StdoutContents())
return nil
}
// CommandList implements buildpacks:list // CommandList implements buildpacks:list
func CommandList(appName string) (err error) { func CommandList(appName string) (err error) {
if err := common.VerifyAppName(appName); err != nil { if err := common.VerifyAppName(appName); err != nil {

View File

@@ -1,9 +1,34 @@
#!/usr/bin/env bash #!/usr/bin/env bash
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/proxy/command-functions"
source "$PLUGIN_AVAILABLE_PATH/caddy-vhosts/internal-functions" source "$PLUGIN_AVAILABLE_PATH/caddy-vhosts/internal-functions"
set -eo pipefail set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x [[ $DOKKU_TRACE ]] && set -x
cmd-caddy-labels-add() {
declare desc="adds container label to app for specified proxy"
declare cmd="caddy:labels:add"
[[ "$1" == "$cmd" ]] && shift 1
cmd-proxy-labels-add "proxy:labels:add" "caddy" "$@"
}
cmd-caddy-labels-remove() {
declare desc="removes container label from app for specified proxy"
declare cmd="caddy:labels:remove"
[[ "$1" == "$cmd" ]] && shift 1
cmd-proxy-labels-remove "proxy:labels:remove" "caddy" "$@"
}
cmd-caddy-labels-show() {
declare desc="shows container labels for app for specified proxy"
declare cmd="caddy:labels:show"
[[ "$1" == "$cmd" ]] && shift 1
cmd-proxy-labels-show "proxy:labels:show" "caddy" "$@"
}
cmd-caddy-report() { cmd-caddy-report() {
declare desc="displays a caddy report for one or more apps" declare desc="displays a caddy report for one or more apps"
declare cmd="caddy:report" declare cmd="caddy:report"

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
[[ " caddy:help help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" [[ " caddy:help caddy:labels:add caddy:labels:remove caddy:labels:show help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
source "$PLUGIN_AVAILABLE_PATH/caddy-vhosts/help-functions" source "$PLUGIN_AVAILABLE_PATH/caddy-vhosts/help-functions"
source "$PLUGIN_AVAILABLE_PATH/caddy-vhosts/command-functions"
set -eo pipefail set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x [[ $DOKKU_TRACE ]] && set -x
@@ -9,6 +10,18 @@ case "$1" in
cmd-caddy-help "$@" cmd-caddy-help "$@"
;; ;;
caddy:labels:add)
cmd-caddy-labels-add "$@"
;;
caddy:labels:remove)
cmd-caddy-labels-remove "$@"
;;
caddy:labels:show)
cmd-caddy-labels-show "$@"
;;
*) *)
exit "$DOKKU_NOT_IMPLEMENTED_EXIT" exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
;; ;;

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/proxy/functions"
source "$PLUGIN_AVAILABLE_PATH/caddy-vhosts/internal-functions" source "$PLUGIN_AVAILABLE_PATH/caddy-vhosts/internal-functions"
set -eo pipefail set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x [[ $DOKKU_TRACE ]] && set -x
@@ -8,7 +9,7 @@ trigger-caddy-vhosts-docker-args-process-deploy() {
declare desc="nginx-vhosts core-post-deploy plugin trigger" declare desc="nginx-vhosts core-post-deploy plugin trigger"
declare trigger="docker-args-process-deploy" declare trigger="docker-args-process-deploy"
declare APP="$1" IMAGE_SOURCE_TYPE="$2" IMAGE_TAG="$3" PROC_TYPE="$4" CONTAINER_INDEX="$5" declare APP="$1" IMAGE_SOURCE_TYPE="$2" IMAGE_TAG="$3" PROC_TYPE="$4" CONTAINER_INDEX="$5"
local app_domains caddy_domains is_app_listening letsencrypt_email output proxy_container_port proxy_host_port port_map proxy_scheme proxy_schemes scheme tls_internal label_key local app_domains caddy_domains is_app_listening letsencrypt_email output proxy_container_port proxy_host_port port_map proxy_scheme proxy_schemes scheme tls_internal
local proxy_container_http_port proxy_container_http_port_candidate proxy_host_http_port_candidate local proxy_container_http_port proxy_container_http_port_candidate proxy_host_http_port_candidate
local proxy_container_https_port proxy_container_https_port_candidate proxy_host_https_port_candidate local proxy_container_https_port proxy_container_https_port_candidate proxy_host_https_port_candidate
local STDIN=$(cat) local STDIN=$(cat)
@@ -88,14 +89,13 @@ trigger-caddy-vhosts-docker-args-process-deploy() {
if [[ -n "$is_app_listening" ]] && [[ -n "$caddy_domains" ]]; then if [[ -n "$is_app_listening" ]] && [[ -n "$caddy_domains" ]]; then
has_443_mapping=false has_443_mapping=false
tls_internal="$(fn-caddy-tls-internal)" tls_internal="$(fn-caddy-tls-internal)"
label_key="$(fn-caddy-label-key "$APP")"
if [[ -n "$proxy_container_https_port" ]] || [[ -n "$proxy_container_https_port_candidate" ]]; then if [[ -n "$proxy_container_https_port" ]] || [[ -n "$proxy_container_https_port_candidate" ]]; then
has_443_mapping=true has_443_mapping=true
fi fi
ssl_warning_mapping="https:443" ssl_warning_mapping="https:443"
if [[ "$tls_internal" == "true" ]]; then if [[ "$tls_internal" == "true" ]]; then
output="--label ${label_key}.tls=internal" output="--label caddy.tls=internal"
if [[ "$has_443_mapping" == "false" ]]; then if [[ "$has_443_mapping" == "false" ]]; then
ssl_warning_mapping="http:80" ssl_warning_mapping="http:80"
proxy_host_https_port_candidate="$proxy_host_http_port_candidate" proxy_host_https_port_candidate="$proxy_host_http_port_candidate"
@@ -106,7 +106,7 @@ trigger-caddy-vhosts-docker-args-process-deploy() {
scheme="http" scheme="http"
if [[ -n "$letsencrypt_email" ]] && [[ "$has_443_mapping" == "true" ]]; then if [[ -n "$letsencrypt_email" ]] && [[ "$has_443_mapping" == "true" ]]; then
output="--label '${label_key}=${caddy_domains}'" output="--label 'caddy=${caddy_domains}'"
scheme="https" scheme="https"
if [[ -z "$proxy_container_https_port" ]]; then if [[ -z "$proxy_container_https_port" ]]; then
warning_scheme="$(awk -F ':' '{ print $1 }' <<<"$ssl_warning_mapping")" warning_scheme="$(awk -F ':' '{ print $1 }' <<<"$ssl_warning_mapping")"
@@ -115,20 +115,28 @@ trigger-caddy-vhosts-docker-args-process-deploy() {
proxy_container_https_port="$proxy_container_https_port_candidate" proxy_container_https_port="$proxy_container_https_port_candidate"
fi fi
output="$output --label \"${label_key}.reverse_proxy={{ upstreams $proxy_container_https_port }}\"" output="$output --label \"caddy.reverse_proxy={{ upstreams $proxy_container_https_port }}\""
elif [[ -n "$proxy_container_http_port" ]] || [[ -n "$proxy_container_http_port_candidate" ]]; then elif [[ -n "$proxy_container_http_port" ]] || [[ -n "$proxy_container_http_port_candidate" ]]; then
caddy_domains="${caddy_domains//, /:80, }" caddy_domains="${caddy_domains//, /:80, }"
output="--label '${label_key}=${caddy_domains}:80'" output="--label 'caddy=${caddy_domains}:80'"
if [[ -z "$proxy_container_http_port" ]]; then if [[ -z "$proxy_container_http_port" ]]; then
dokku_log_warn "Warning: http:80 port mapping not found" dokku_log_warn "Warning: http:80 port mapping not found"
dokku_log_warn "Utilizing first http port mapping, http:$proxy_host_http_port_candidate:$proxy_container_http_port_candidate" dokku_log_warn "Utilizing first http port mapping, http:$proxy_host_http_port_candidate:$proxy_container_http_port_candidate"
proxy_container_http_port="$proxy_container_http_port_candidate" proxy_container_http_port="$proxy_container_http_port_candidate"
fi fi
output="$output --label \"${label_key}.reverse_proxy={{ upstreams $proxy_container_http_port }}\"" output="$output --label \"caddy.reverse_proxy={{ upstreams $proxy_container_http_port }}\""
fi fi
fi fi
local proxy_labels_file_path=$(fn-proxy-get-labels-file-path "caddy" "$APP")
if [[ -f "$proxy_labels_file_path" ]]; then
while read -r line; do
[[ -z "$line" ]] && continue
output="$output --label '$line'"
done <"$proxy_labels_file_path"
fi
echo -n "$STDIN$output" echo -n "$STDIN$output"
} }

View File

@@ -30,6 +30,9 @@ fn-help-content() {
caddy:report [<app>] [<flag>], Displays an caddy report for one or more apps caddy:report [<app>] [<flag>], Displays an caddy report for one or more apps
caddy:set <app> <property> (<value>), Set or clear an caddy property for an app caddy:set <app> <property> (<value>), Set or clear an caddy property for an app
caddy:show-config <app>, Display caddy compose config caddy:show-config <app>, Display caddy compose config
caddy:labels:add <app> <label> <value>, Add a label to an caddy app
caddy:labels:remove <app> <label>, Remove a label from an caddy app
caddy:labels:show <app> [<label>], Show labels for an caddy app
caddy:start, Starts the caddy server caddy:start, Starts the caddy server
caddy:stop, Stops the caddy server caddy:stop, Stops the caddy server
help_content help_content

View File

@@ -71,8 +71,3 @@ fn-caddy-tls-internal() {
declare APP="$1" declare APP="$1"
fn-plugin-property-get-default "caddy" "$APP" "tls-internal" "false" fn-plugin-property-get-default "caddy" "$APP" "tls-internal" "false"
} }
fn-caddy-label-key() {
declare APP="$1"
fn-plugin-property-get-default "caddy" "$APP" "label-key" "caddy"
}

View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
trigger-caddy-vhosts-post-create() {
declare desc="caddy-vhosts post-create trigger"
declare trigger="post-create"
declare APP="$1"
fn-plugin-property-setup-app "caddy" "$APP"
}
trigger-caddy-vhosts-post-create "$@"

View File

@@ -9,13 +9,13 @@ cmd-caddy-set() {
declare cmd="caddy:set" declare cmd="caddy:set"
[[ "$1" == "$cmd" ]] && shift 1 [[ "$1" == "$cmd" ]] && shift 1
declare APP="$1" KEY="$2" VALUE="$3" declare APP="$1" KEY="$2" VALUE="$3"
local VALID_KEYS=("image" "letsencrypt-email" "letsencrypt-server" "log-level" "polling-interval" "label-key" "tls-internal") local VALID_KEYS=("image" "letsencrypt-email" "letsencrypt-server" "log-level" "polling-interval" "tls-internal")
local GLOBAL_KEYS=("image" "letsencrypt-email" "letsencrypt-server" "log-level" "polling-interval") local GLOBAL_KEYS=("image" "letsencrypt-email" "letsencrypt-server" "log-level" "polling-interval")
[[ -z "$KEY" ]] && dokku_log_fail "No key specified" [[ -z "$KEY" ]] && dokku_log_fail "No key specified"
if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then
dokku_log_fail "Invalid key specified, valid keys include: image letsencrypt-email letsencrypt-server log-level polling-interval label-key tls-internal" dokku_log_fail "Invalid key specified, valid keys include: image letsencrypt-email letsencrypt-server log-level polling-interval tls-internal"
fi fi
if ! fn-in-array "$KEY" "${GLOBAL_KEYS[@]}"; then if ! fn-in-array "$KEY" "${GLOBAL_KEYS[@]}"; then

View File

@@ -29,6 +29,9 @@ type ExecCommandInput struct {
// Env is the environment variables to pass to the command // Env is the environment variables to pass to the command
Env map[string]string Env map[string]string
// PrintCommand prints the command before executing
PrintCommand bool
// Stdin is the stdin of the command // Stdin is the stdin of the command
Stdin io.Reader Stdin io.Reader
@@ -141,7 +144,7 @@ func CallExecCommandWithContext(ctx context.Context, input ExecCommandInput) (Ex
cmd.Cwd = input.WorkingDirectory cmd.Cwd = input.WorkingDirectory
} }
if os.Getenv("DOKKU_TRACE") == "1" { if input.PrintCommand || os.Getenv("DOKKU_TRACE") == "1" {
argsSt := "" argsSt := ""
if len(cmd.Args) > 0 { if len(cmd.Args) > 0 {
argsSt = strings.Join(cmd.Args, " ") argsSt = strings.Join(cmd.Args, " ")

View File

@@ -665,11 +665,6 @@ dokku_receive() {
docker_cleanup "$APP" docker_cleanup "$APP"
local DOKKU_CNB_EXPERIMENTAL="$(config_get "$APP" DOKKU_CNB_EXPERIMENTAL || true)"
if [[ "$DOKKU_CNB_EXPERIMENTAL" == "1" ]]; then
IMAGE_SOURCE_TYPE="pack"
fi
plugn trigger builder-set-property "$APP" "detected" "$IMAGE_SOURCE_TYPE" plugn trigger builder-set-property "$APP" "detected" "$IMAGE_SOURCE_TYPE"
if ! dokku_build "$APP" "$IMAGE_SOURCE_TYPE" "$TMP_WORK_DIR"; then if ! dokku_build "$APP" "$IMAGE_SOURCE_TYPE" "$TMP_WORK_DIR"; then
return 1 return 1

View File

@@ -90,7 +90,9 @@ func LogFailWithError(err error) {
fmt.Fprintf(os.Stderr, " ! %s\n", e.Error()) fmt.Fprintf(os.Stderr, " ! %s\n", e.Error())
} }
} else { } else {
fmt.Fprintf(os.Stderr, " ! %s\n", err.Error()) if err.Error() != "" {
fmt.Fprintf(os.Stderr, " ! %s\n", err.Error())
}
} }
if errExit, ok := err.(ErrWithExitCode); ok { if errExit, ok := err.(ErrWithExitCode); ok {
os.Exit(errExit.ExitCode()) os.Exit(errExit.ExitCode())

View File

@@ -19,6 +19,9 @@ type PlugnTriggerInput struct {
// Env is the environment variables to pass to the trigger // Env is the environment variables to pass to the trigger
Env map[string]string Env map[string]string
// PrintCommand prints the command before executing
PrintCommand bool
// Stdin is the stdin of the command // Stdin is the stdin of the command
Stdin io.Reader Stdin io.Reader
@@ -55,7 +58,7 @@ func CallPlugnTriggerWithContext(ctx context.Context, input PlugnTriggerInput) (
StreamStderr: input.StreamStderr, 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") { for _, line := range strings.Split(result.Stderr, "\n") {
LogDebug(fmt.Sprintf("plugn trigger %s stderr: %s", input.Trigger, line)) LogDebug(fmt.Sprintf("plugn trigger %s stderr: %s", input.Trigger, line))
} }

View File

@@ -12,27 +12,27 @@ import (
) )
// CommandPropertySet is a generic function that will set a property for a given plugin/app combination // CommandPropertySet is a generic function that will set a property for a given plugin/app combination
func CommandPropertySet(pluginName, appName, property, value string, properties map[string]string, globalProperties map[string]bool) { func CommandPropertySet(pluginName, appName, property, value string, validProperties map[string]string, validGlobalProperties map[string]bool) {
if appName != "--global" { if appName != "--global" {
if err := VerifyAppName(appName); err != nil { if err := VerifyAppName(appName); err != nil {
LogFailWithError(err) LogFailWithError(err)
} }
} }
if appName == "--global" && !globalProperties[property] { if appName == "--global" && !validGlobalProperties[property] {
LogFail("Property cannot be specified globally") LogFail("Property cannot be specified globally")
} }
if property == "" { if property == "" {
LogFail("No property specified") LogFail("No property specified")
} }
for k := range globalProperties { for k := range validGlobalProperties {
if _, ok := properties[k]; !ok { if _, ok := validProperties[k]; !ok {
properties[k] = "" validProperties[k] = ""
} }
} }
if _, ok := properties[property]; !ok { if _, ok := validProperties[property]; !ok {
properties := reflect.ValueOf(properties).MapKeys() properties := reflect.ValueOf(validProperties).MapKeys()
validPropertyList := make([]string, len(properties)) validPropertyList := make([]string, len(properties))
for i := 0; i < len(properties); i++ { for i := 0; i < len(properties); i++ {
validPropertyList[i] = properties[i].String() validPropertyList[i] = properties[i].String()
@@ -540,6 +540,14 @@ func PropertySetup(pluginName string) error {
}) })
} }
func PropertySetupApp(pluginName string, appName string) error {
if err := makePluginAppPropertyPath(pluginName, appName); err != nil {
return fmt.Errorf("Unable to create %s config directory for %s: %s", pluginName, appName, err.Error())
}
return nil
}
func getPropertyPath(pluginName string, appName string, property string) string { func getPropertyPath(pluginName string, appName string, property string) string {
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName) pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
return filepath.Join(pluginAppConfigRoot, property) return filepath.Join(pluginAppConfigRoot, property)

View File

@@ -104,3 +104,9 @@ fn-plugin-property-setup() {
declare PLUGIN="$1" declare PLUGIN="$1"
"$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "setup" "$PLUGIN" "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "setup" "$PLUGIN"
} }
fn-plugin-property-setup-app() {
declare desc="setup the properties for an app"
declare PLUGIN="$1" APP="$2"
"$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "setup-app" "$PLUGIN" "$APP"
}

View File

@@ -219,7 +219,7 @@ func (task SftpCopyTask) Execute(ctx context.Context) (SftpCopyResult, error) {
}, ctx.Err() }, 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)) LogDebug(fmt.Sprintf("ssh %s@%s cp %s %v", task.Username, task.Hostname, task.SourcePath, task.DestinationPath))
} }

View File

@@ -194,5 +194,12 @@ func main() {
fmt.Fprintln(os.Stderr, err.Error()) fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
case "setup-app":
appName := flag.Arg(2)
err := common.PropertySetupApp(pluginName, appName)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
} }
} }

View File

@@ -203,6 +203,7 @@ type SshTask struct {
// PrintCommand prints the command before executing // PrintCommand prints the command before executing
PrintCommand bool PrintCommand bool
// StreamStdio prints stdout and stderr directly to os.Stdout/err as // StreamStdio prints stdout and stderr directly to os.Stdout/err as
// the command runs. // the command runs.
StreamStdio bool StreamStdio bool
@@ -334,7 +335,7 @@ func (task SshTask) Execute(ctx context.Context) (SshResult, error) {
command = "sudo" 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)) LogDebug(fmt.Sprintf("ssh %s@%s %s %v", task.Username, task.Hostname, command, commandArgs))
} }

View File

@@ -1,5 +1,5 @@
GOARCH ?= amd64 GOARCH ?= amd64
SUBCOMMANDS = subcommands/bundle subcommands/clear subcommands/export subcommands/get subcommands/keys subcommands/show subcommands/set subcommands/unset SUBCOMMANDS = subcommands/bundle subcommands/clear subcommands/export subcommands/get subcommands/import subcommands/keys subcommands/show subcommands/set subcommands/unset
TRIGGERS = triggers/config-export triggers/config-get triggers/config-get-global triggers/config-unset triggers/post-app-clone-setup triggers/post-app-rename-setup TRIGGERS = triggers/config-export triggers/config-get triggers/config-get-global triggers/config-unset triggers/post-app-clone-setup triggers/post-app-rename-setup
BUILD = commands config_sub subcommands triggers BUILD = commands config_sub subcommands triggers
PLUGIN_NAME = config PLUGIN_NAME = config

View File

@@ -30,7 +30,7 @@ func GetWithDefault(appName string, key string, defaultValue string) (value stri
} }
// SetMany variables in the environment. If appName is empty the global config is used. If restart is true the app is restarted. // SetMany variables in the environment. If appName is empty the global config is used. If restart is true the app is restarted.
func SetMany(appName string, entries map[string]string, restart bool) (err error) { func SetMany(appName string, entries map[string]string, replace bool, restart bool) (err error) {
global := appName == "" || appName == "--global" global := appName == "" || appName == "--global"
env, err := loadAppOrGlobalEnv(appName) env, err := loadAppOrGlobalEnv(appName)
if err != nil { if err != nil {
@@ -42,6 +42,10 @@ func SetMany(appName string, entries map[string]string, restart bool) (err error
return return
} }
} }
if replace {
env.Clear()
}
for k, v := range entries { for k, v := range entries {
env.Set(k, v) env.Set(k, v)
keys = append(keys, k) keys = append(keys, k)
@@ -128,6 +132,10 @@ func UnsetAll(appName string, restart bool) (err error) {
} }
func triggerRestart(appName string) { func triggerRestart(appName string) {
if !common.IsDeployed(appName) {
return
}
common.LogInfo1(fmt.Sprintf("Restarting app %s", appName)) common.LogInfo1(fmt.Sprintf("Restarting app %s", appName))
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{ _, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "release-and-deploy", Trigger: "release-and-deploy",

View File

@@ -128,6 +128,82 @@ func TestConfigUnsetMany(t *testing.T) {
Expect(CommandUnset(testAppName+"does-not-exist", keys, false, true)).ToNot(Succeed()) Expect(CommandUnset(testAppName+"does-not-exist", keys, false, true)).ToNot(Succeed())
} }
func TestConfigImport(t *testing.T) {
RegisterTestingT(t)
Expect(setupTests()).To(Succeed())
Expect(setupTestApp()).To(Succeed())
defer teardownTestApp()
tempFile, err := os.CreateTemp("", "test-config-import-*.env")
Expect(err).To(Succeed())
defer os.Remove(tempFile.Name())
content := `
testKey=TESTING-updated1
testKey2=TESTING-updated2
`
_, err = tempFile.WriteString(content)
Expect(err).To(Succeed())
tempFile.Close()
Expect(CommandImport(testAppName, false, false, true, "envfile", tempFile.Name())).To(Succeed())
expectValue(testAppName, "testKey", "TESTING-updated1")
expectValue(testAppName, "testKey2", "TESTING-updated2")
env, err := LoadAppEnv(testAppName)
Expect(err).To(Succeed())
env.Set("testKey", "TESTING-original1")
env.Set("testKey2", "TESTING-original2")
env.Set("testKey3", "TESTING-original3")
env.Write()
Expect(CommandImport(testAppName, false, false, true, "envfile", tempFile.Name())).To(Succeed())
expectValue(testAppName, "testKey", "TESTING-updated1")
expectValue(testAppName, "testKey2", "TESTING-updated2")
expectValue(testAppName, "testKey3", "TESTING-original3")
Expect(CommandImport(testAppName, false, true, true, "envfile", tempFile.Name())).To(Succeed())
expectValue(testAppName, "testKey", "TESTING-updated1")
expectValue(testAppName, "testKey2", "TESTING-updated2")
expectNoValue(testAppName, "testKey3")
}
func TestConfigImportJSON(t *testing.T) {
RegisterTestingT(t)
Expect(setupTests()).To(Succeed())
Expect(setupTestApp()).To(Succeed())
defer teardownTestApp()
tempFile, err := os.CreateTemp("", "test-config-import-*.json")
Expect(err).To(Succeed())
defer os.Remove(tempFile.Name())
content := `{"testKey": "TESTING-updated1", "testKey2": "TESTING-updated2"}`
_, err = tempFile.WriteString(content)
Expect(err).To(Succeed())
tempFile.Close()
Expect(CommandImport(testAppName, false, false, true, "json", tempFile.Name())).To(Succeed())
expectValue(testAppName, "testKey", "TESTING-updated1")
expectValue(testAppName, "testKey2", "TESTING-updated2")
env, err := LoadAppEnv(testAppName)
Expect(err).To(Succeed())
env.Set("testKey", "TESTING-original1")
env.Set("testKey2", "TESTING-original2")
env.Set("testKey3", "TESTING-original3")
env.Write()
Expect(CommandImport(testAppName, false, false, true, "json", tempFile.Name())).To(Succeed())
expectValue(testAppName, "testKey", "TESTING-updated1")
expectValue(testAppName, "testKey2", "TESTING-updated2")
expectValue(testAppName, "testKey3", "TESTING-original3")
Expect(CommandImport(testAppName, false, true, true, "json", tempFile.Name())).To(Succeed())
expectValue(testAppName, "testKey", "TESTING-updated1")
expectValue(testAppName, "testKey2", "TESTING-updated2")
expectNoValue(testAppName, "testKey3")
}
func TestEnvironmentLoading(t *testing.T) { func TestEnvironmentLoading(t *testing.T) {
RegisterTestingT(t) RegisterTestingT(t)
Expect(setupTests()).To(Succeed()) Expect(setupTests()).To(Succeed())
@@ -161,12 +237,13 @@ func TestInvalidKeys(t *testing.T) {
invalidKeys := []string{"0invalidKey", "invalid:key", "invalid=Key", "!invalidKey"} invalidKeys := []string{"0invalidKey", "invalid:key", "invalid=Key", "!invalidKey"}
for _, key := range invalidKeys { for _, key := range invalidKeys {
Expect(SetMany(testAppName, map[string]string{key: "value"}, false)).NotTo(Succeed()) Expect(SetMany(testAppName, map[string]string{key: "value"}, false, false)).NotTo(Succeed())
Expect(UnsetMany(testAppName, []string{key}, false)).NotTo(Succeed()) Expect(UnsetMany(testAppName, []string{key}, false)).NotTo(Succeed())
value, ok := Get(testAppName, key) value, ok := Get(testAppName, key)
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
value = GetWithDefault(testAppName, key, "default") Expect(value).To(Equal(""))
Expect(value).To(Equal("default")) value2 := GetWithDefault(testAppName, key, "default")
Expect(value2).To(Equal("default"))
} }
} }

View File

@@ -88,6 +88,11 @@ func LoadGlobalEnv() (*Env, error) {
return loadFromFile("<global>", getGlobalFile()) return loadFromFile("<global>", getGlobalFile())
} }
// Clear clears the environment
func (e *Env) Clear() {
e.env = make(map[string]string)
}
// Filename returns the full path on disk to the file holding the env vars // Filename returns the full path on disk to the file holding the env vars
func (e *Env) Filename() string { func (e *Env) Filename() string {
return e.filename return e.filename
@@ -326,8 +331,17 @@ func prettyPrintEnvEntries(prefix string, entries map[string]string) string {
func loadFromFile(name string, filename string) (env *Env, err error) { func loadFromFile(name string, filename string) (env *Env, err error) {
envMap := make(map[string]string) envMap := make(map[string]string)
if filename == "-" {
envMap, err = godotenv.Parse(os.Stdin)
if err != nil {
return nil, err
}
}
if _, err := os.Stat(filename); err == nil { if _, err := os.Stat(filename); err == nil {
envMap, err = godotenv.Read(filename) envMap, err = godotenv.Read(filename)
if err != nil {
return nil, err
}
} }
dirty := false dirty := false
@@ -352,6 +366,31 @@ func loadFromFile(name string, filename string) (env *Env, err error) {
return return
} }
func loadFromFileJSON(name string, filename string) (env *Env, err error) {
envMap := make(map[string]string)
if filename == "-" {
err = json.NewDecoder(os.Stdin).Decode(&envMap)
if err != nil {
return nil, err
}
} else {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
err = json.NewDecoder(file).Decode(&envMap)
if err != nil {
return nil, err
}
}
return &Env{
name: name,
filename: "",
env: envMap,
}, nil
}
func getAppFile(appName string) (string, error) { func getAppFile(appName string) (string, error) {
return filepath.Join(common.MustGetEnv("DOKKU_ROOT"), appName, "ENV"), nil return filepath.Join(common.MustGetEnv("DOKKU_ROOT"), appName, "ENV"), nil
} }

View File

@@ -82,6 +82,44 @@ func SubGet(appName string, keys []string, quoted bool) error {
return nil return nil
} }
// SubImport imports environment variables from a file
func SubImport(appName string, replace bool, noRestart bool, format string, filename string) error {
if filename == "-" {
common.LogInfo1Quiet("Reading config vars from stdin")
}
if format == "" {
format = "envfile"
}
var env *Env
var err error
switch format {
case "envfile":
env, err = loadFromFile(appName, filename)
if err != nil {
return err
}
case "json":
env, err = loadFromFileJSON(appName, filename)
if err != nil {
return err
}
default:
return fmt.Errorf("Unknown format: %s", format)
}
if len(env.Map()) == 0 {
return errors.New("No config vars to set")
}
for k, v := range env.Map() {
common.LogInfo1Quiet(fmt.Sprintf("Setting config var %s=%s", k, v))
}
return SetMany(appName, env.Map(), replace, !noRestart)
}
// SubKeys implements the logic for config:keys without app name validation // SubKeys implements the logic for config:keys without app name validation
func SubKeys(appName string, merged bool) error { func SubKeys(appName string, merged bool) error {
env := getEnvironment(appName, merged) env := getEnvironment(appName, merged)
@@ -115,7 +153,7 @@ func SubSet(appName string, pairs []string, noRestart bool, encoded bool) error
updated[key] = value updated[key] = value
} }
return SetMany(appName, updated, !noRestart) return SetMany(appName, updated, false, !noRestart)
} }
// SubShow implements the logic for config:show without app name validation // SubShow implements the logic for config:show without app name validation

View File

@@ -24,6 +24,7 @@ Additional commands:`
config:clear [--no-restart] (<app>|--global), Clears environment variables config:clear [--no-restart] (<app>|--global), Clears environment variables
config:export [--format=FORMAT] [--merged] (<app>|--global), Export a global or app environment config:export [--format=FORMAT] [--merged] (<app>|--global), Export a global or app environment
config:get [--quoted] (<app>|--global) KEY, Display a global or app-specific config value config:get [--quoted] (<app>|--global) KEY, Display a global or app-specific config value
config:import [--no-restart] [--replace] (<app>|--global) [FILE|-], Import environment from file
config:keys [--merged] (<app>|--global), Show keys set in environment config:keys [--merged] (<app>|--global), Show keys set in environment
config:show [--merged] (<app>|--global), Show keys set in environment config:show [--merged] (<app>|--global), Show keys set in environment
config:set [--encoded] [--no-restart] (<app>|--global) KEY1=VALUE1 [KEY2=VALUE2 ...], Set one or more config vars config:set [--encoded] [--no-restart] (<app>|--global) KEY1=VALUE1 [KEY2=VALUE2 ...], Set one or more config vars

View File

@@ -65,6 +65,22 @@ func main() {
} }
keys := getKeys(args.Args(), *global) keys := getKeys(args.Args(), *global)
err = config.CommandGet(appName, keys, *global, *quoted) err = config.CommandGet(appName, keys, *global, *quoted)
case "import":
args := flag.NewFlagSet("config:import", flag.ExitOnError)
global := args.Bool("global", false, "--global: use the global environment")
noRestart := args.Bool("no-restart", false, "--no-restart: no restart")
replace := args.Bool("replace", false, "--replace: replace existing config vars")
format := args.String("format", "envfile", "--format: [ envfile | json ] which format to export as)")
args.Parse(os.Args[2:])
var filename string
if !*global {
appName = args.Arg(0)
filename = args.Arg(1)
} else {
filename = args.Arg(0)
}
err = config.CommandImport(appName, *global, *replace, *noRestart, *format, filename)
case "keys": case "keys":
args := flag.NewFlagSet("config:keys", flag.ExitOnError) args := flag.NewFlagSet("config:keys", flag.ExitOnError)
global := args.Bool("global", false, "--global: use the global environment") global := args.Bool("global", false, "--global: use the global environment")

View File

@@ -42,6 +42,16 @@ func CommandGet(appName string, keys []string, global bool, quoted bool) error {
return SubGet(appName, keys, quoted) return SubGet(appName, keys, quoted)
} }
// CommandImport imports environment variables from a file
func CommandImport(appName string, global bool, replace bool, noRestart bool, format string, filename string) error {
appName, err := getAppNameOrGlobal(appName, global)
if err != nil {
return err
}
return SubImport(appName, replace, noRestart, format, filename)
}
// CommandKeys shows the keys set for the specified environment // CommandKeys shows the keys set for the specified environment
func CommandKeys(appName string, global bool, merged bool) error { func CommandKeys(appName string, global bool, merged bool) error {
appName, err := getAppNameOrGlobal(appName, global) appName, err := getAppNameOrGlobal(appName, global)

View File

@@ -1,4 +1,4 @@
SUBCOMMANDS = subcommands/list subcommands/report subcommands/run subcommands/set SUBCOMMANDS = subcommands/list subcommands/report subcommands/resume subcommands/run subcommands/set subcommands/suspend
TRIGGERS = triggers/app-json-is-valid triggers/cron-get-property triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/scheduler-stop TRIGGERS = triggers/app-json-is-valid triggers/cron-get-property triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/scheduler-stop
BUILD = commands subcommands triggers BUILD = commands subcommands triggers
PLUGIN_NAME = cron PLUGIN_NAME = cron

View File

@@ -2,6 +2,7 @@ package cron
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
appjson "github.com/dokku/dokku/plugins/app-json" appjson "github.com/dokku/dokku/plugins/app-json"
@@ -27,6 +28,8 @@ var (
} }
) )
const MaintenancePropertyPrefix = "maintenance."
// CronTask is a struct that represents a cron task // CronTask is a struct that represents a cron task
type CronTask struct { type CronTask struct {
// ID is a unique identifier for the cron task // ID is a unique identifier for the cron task
@@ -44,12 +47,21 @@ type CronTask struct {
// Schedule is the cron schedule // Schedule is the cron schedule
Schedule string `json:"schedule"` Schedule string `json:"schedule"`
// ConcurrencyPolicy is the concurrency policy for the cron command
ConcurrencyPolicy string `json:"concurrency_policy"`
// AltCommand is an alternate command to run // AltCommand is an alternate command to run
AltCommand string `json:"-"` AltCommand string `json:"-"`
// LogFile is the log file to write to // LogFile is the log file to write to
LogFile string `json:"-"` LogFile string `json:"-"`
// AppInMaintenance is whether the app's cron is in maintenance mode
AppInMaintenance bool `json:"app-in-maintenance"`
// Maintenance is whether the cron task is in maintenance mode
TaskInMaintenance bool `json:"task-in-maintenance"`
// Maintenance is whether the cron task is in maintenance mode // Maintenance is whether the cron task is in maintenance mode
Maintenance bool `json:"maintenance"` Maintenance bool `json:"maintenance"`
} }
@@ -63,7 +75,7 @@ func (t CronTask) DokkuRunCommand() string {
return t.AltCommand return t.AltCommand
} }
return fmt.Sprintf("dokku run --cron-id %s %s %s", t.ID, t.App, t.Command) return fmt.Sprintf("dokku run --concurrency-policy %s --cron-id %s %s %s", t.ConcurrencyPolicy, t.ID, t.App, t.Command)
} }
// FetchCronTasksInput is the input for the FetchCronTasks function // FetchCronTasksInput is the input for the FetchCronTasks function
@@ -77,7 +89,7 @@ type FetchCronTasksInput struct {
func FetchCronTasks(input FetchCronTasksInput) ([]CronTask, error) { func FetchCronTasks(input FetchCronTasksInput) ([]CronTask, error) {
appName := input.AppName appName := input.AppName
tasks := []CronTask{} tasks := []CronTask{}
isMaintenance := reportComputedMaintenance(appName) == "true" isAppCronInMaintenance := reportComputedMaintenance(appName) == "true"
if input.AppJSON == nil && input.AppName == "" { if input.AppJSON == nil && input.AppName == "" {
return tasks, fmt.Errorf("Missing app name or app.json") return tasks, fmt.Errorf("Missing app name or app.json")
@@ -96,6 +108,11 @@ func FetchCronTasks(input FetchCronTasksInput) ([]CronTask, error) {
return tasks, nil return tasks, nil
} }
properties, err := common.PropertyGetAllByPrefix("cron", appName, MaintenancePropertyPrefix)
if err != nil {
return tasks, fmt.Errorf("Error getting maintenance properties: %w", err)
}
for i, c := range input.AppJSON.Cron { for i, c := range input.AppJSON.Cron {
if c.Command == "" { if c.Command == "" {
if input.WarnToFailure { if input.WarnToFailure {
@@ -121,12 +138,35 @@ func FetchCronTasks(input FetchCronTasksInput) ([]CronTask, error) {
return tasks, fmt.Errorf("Invalid cron schedule for app %s (schedule %s): %s", appName, c.Schedule, err.Error()) return tasks, fmt.Errorf("Invalid cron schedule for app %s (schedule %s): %s", appName, c.Schedule, err.Error())
} }
cronID := GenerateCommandID(appName, c)
maintenance := c.Maintenance
if value, ok := properties[MaintenancePropertyPrefix+cronID]; ok {
boolValue, err := strconv.ParseBool(value)
if err != nil {
return tasks, fmt.Errorf("Invalid maintenance property for app %s (schedule %s): %s", appName, c.Schedule, err.Error())
}
// only override the maintenance value if the property is set to true
if boolValue {
maintenance = boolValue
}
}
if c.ConcurrencyPolicy == "" {
c.ConcurrencyPolicy = "allow"
}
if c.ConcurrencyPolicy != "allow" && c.ConcurrencyPolicy != "forbid" && c.ConcurrencyPolicy != "replace" {
return tasks, fmt.Errorf("Invalid cron concurrency policy for app %s (schedule %s): %s", appName, c.Schedule, c.ConcurrencyPolicy)
}
tasks = append(tasks, CronTask{ tasks = append(tasks, CronTask{
App: appName, App: appName,
Command: c.Command, Command: c.Command,
Schedule: c.Schedule, Schedule: c.Schedule,
ID: GenerateCommandID(appName, c), ID: cronID,
Maintenance: isMaintenance, ConcurrencyPolicy: c.ConcurrencyPolicy,
Maintenance: isAppCronInMaintenance || maintenance,
AppInMaintenance: isAppCronInMaintenance,
TaskInMaintenance: maintenance,
}) })
} }
@@ -155,12 +195,14 @@ func FetchGlobalCronTasks() ([]CronTask, error) {
id := base36.EncodeToStringLc([]byte(strings.Join(parts, ";;;"))) id := base36.EncodeToStringLc([]byte(strings.Join(parts, ";;;")))
task := CronTask{ task := CronTask{
ID: id, ID: id,
Schedule: parts[0], Schedule: parts[0],
Command: parts[1], Command: parts[1],
AltCommand: parts[1], AltCommand: parts[1],
Maintenance: false, Global: true,
Global: true, Maintenance: false,
TaskInMaintenance: false,
AppInMaintenance: false,
} }
if len(parts) == 3 { if len(parts) == 3 {
task.LogFile = parts[2] task.LogFile = parts[2]

View File

@@ -26,6 +26,7 @@ require (
github.com/otiai10/mint v1.6.3 // indirect github.com/otiai10/mint v1.6.3 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/sftp v1.13.5 // indirect github.com/pkg/sftp v1.13.5 // indirect
github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a // indirect
golang.org/x/crypto v0.43.0 // indirect golang.org/x/crypto v0.43.0 // indirect
golang.org/x/sync v0.17.0 // indirect golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect golang.org/x/sys v0.37.0 // indirect

View File

@@ -51,8 +51,11 @@ github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a h1:a6TNDN9CgG+cYjaeN8l2mc4kSz2iMiCDQxPEyltUV/I=
github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=

View File

@@ -1,7 +1,9 @@
package cron package cron
import ( import (
"fmt"
"strconv" "strconv"
"strings"
"github.com/dokku/dokku/plugins/common" "github.com/dokku/dokku/plugins/common"
) )
@@ -21,6 +23,11 @@ func ReportSingleApp(appName string, format string, infoFlag string) error {
"--cron-maintenance": reportMaintenance, "--cron-maintenance": reportMaintenance,
} }
extraFlags := addCronMaintenanceFlags(appName, infoFlag)
for flag, fn := range extraFlags {
flags[flag] = fn
}
flagKeys := []string{} flagKeys := []string{}
for flagKey := range flags { for flagKey := range flags {
flagKeys = append(flagKeys, flagKey) flagKeys = append(flagKeys, flagKey)
@@ -32,6 +39,24 @@ func ReportSingleApp(appName string, format string, infoFlag string) error {
return common.ReportSingleApp("cron", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) return common.ReportSingleApp("cron", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter)
} }
func addCronMaintenanceFlags(appName string, infoFlag string) map[string]common.ReportFunc {
flags := map[string]common.ReportFunc{}
properties, err := common.PropertyGetAllByPrefix("cron", appName, MaintenancePropertyPrefix)
if err != nil {
return flags
}
for property, value := range properties {
key := strings.Replace(property, MaintenancePropertyPrefix, "", 1)
flags[fmt.Sprintf("--cron-maintenance-%s", key)] = func(appName string) string {
return value
}
}
return flags
}
func reportMailfrom(_ string) string { func reportMailfrom(_ string) string {
return common.PropertyGet("cron", "--global", "mailfrom") return common.PropertyGet("cron", "--global", "mailfrom")
} }

View File

@@ -20,8 +20,10 @@ Additional commands:`
helpContent = ` helpContent = `
cron:list <app> [--format json|stdout], List scheduled cron tasks for an app cron:list <app> [--format json|stdout], List scheduled cron tasks for an app
cron:report [<app>] [<flag>], Display report about an app cron:report [<app>] [<flag>], Display report about an app
cron:resume <app> <cron_id>, Resume a cron task
cron:run <app> <cron_id> [--detach], Run a cron task on the fly cron:run <app> <cron_id> [--detach], Run a cron task on the fly
cron:set [--global|<app>] <key> <value>, Set or clear a cron property for an app` cron:set [--global|<app>] <key> <value>, Set or clear a cron property for an app
cron:suspend <app> <cron_id>, Suspend a cron task`
) )
func main() { func main() {

View File

@@ -37,6 +37,12 @@ func main() {
appName := args.Arg(0) appName := args.Arg(0)
err = cron.CommandReport(appName, *format, infoFlag) err = cron.CommandReport(appName, *format, infoFlag)
} }
case "resume":
args := flag.NewFlagSet("cron:resume", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
cronID := args.Arg(1)
err = cron.CommandResume(appName, cronID)
case "run": case "run":
args := flag.NewFlagSet("cron:run", flag.ExitOnError) args := flag.NewFlagSet("cron:run", flag.ExitOnError)
detached := args.Bool("detach", false, "--detach: run the container in a detached mode") detached := args.Bool("detach", false, "--detach: run the container in a detached mode")
@@ -57,6 +63,12 @@ func main() {
value = args.Arg(1) value = args.Arg(1)
} }
err = cron.CommandSet(appName, property, value) err = cron.CommandSet(appName, property, value)
case "suspend":
args := flag.NewFlagSet("cron:suspend", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
cronID := args.Arg(1)
err = cron.CommandSuspend(appName, cronID)
default: default:
err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand) err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand)
} }

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"strings"
"github.com/dokku/dokku/plugins/common" "github.com/dokku/dokku/plugins/common"
@@ -41,9 +42,17 @@ func CommandList(appName string, format string) error {
} }
if format == "stdout" { if format == "stdout" {
output := []string{"ID | Schedule | Maintenance | Command"} output := []string{"ID | Schedule | Concurrency | Maintenance | Command"}
for _, task := range tasks { for _, task := range tasks {
output = append(output, fmt.Sprintf("%s | %s | %t | %s", task.ID, task.Schedule, task.Maintenance, task.Command)) maintenance := "false"
if task.Maintenance {
if task.TaskInMaintenance {
maintenance = "true (task)"
} else if task.AppInMaintenance {
maintenance = "true (app)"
}
}
output = append(output, fmt.Sprintf("%s | %s | %s | %t | %s", task.ID, task.Schedule, task.ConcurrencyPolicy, maintenance, task.Command))
} }
result := columnize.SimpleFormat(output) result := columnize.SimpleFormat(output)
@@ -82,6 +91,11 @@ func CommandReport(appName string, format string, infoFlag string) error {
return ReportSingleApp(appName, format, infoFlag) return ReportSingleApp(appName, format, infoFlag)
} }
// CommandResume resumes a cron task
func CommandResume(appName string, cronID string) error {
return CommandSet(appName, fmt.Sprintf("%s%s", MaintenancePropertyPrefix, cronID), "")
}
// CommandRun executes a cron task on the fly // CommandRun executes a cron task on the fly
func CommandRun(appName string, cronID string, detached bool) error { func CommandRun(appName string, cronID string, detached bool) error {
if err := common.VerifyAppName(appName); err != nil { if err := common.VerifyAppName(appName); err != nil {
@@ -98,9 +112,11 @@ func CommandRun(appName string, cronID string, detached bool) error {
} }
command := "" command := ""
concurrencyPolicy := "allow"
for _, task := range tasks { for _, task := range tasks {
if task.ID == cronID { if task.ID == cronID {
command = task.Command command = task.Command
concurrencyPolicy = task.ConcurrencyPolicy
} }
} }
@@ -120,6 +136,7 @@ func CommandRun(appName string, cronID string, detached bool) error {
os.Setenv("DOKKU_DISABLE_TTY", "true") os.Setenv("DOKKU_DISABLE_TTY", "true")
} }
os.Setenv("DOKKU_CONCURRENCY_POLICY", concurrencyPolicy)
os.Setenv("DOKKU_CRON_ID", cronID) os.Setenv("DOKKU_CRON_ID", cronID)
os.Setenv("DOKKU_RM_CONTAINER", "1") os.Setenv("DOKKU_RM_CONTAINER", "1")
scheduler := common.GetAppScheduler(appName) scheduler := common.GetAppScheduler(appName)
@@ -129,6 +146,11 @@ func CommandRun(appName string, cronID string, detached bool) error {
Args: args, Args: args,
StreamStdio: true, StreamStdio: true,
}) })
if err != nil {
// return an error with an empty message to avoid
// printing the error message twice
return errors.New("")
}
return err return err
} }
@@ -138,7 +160,37 @@ func CommandSet(appName string, property string, value string) error {
return err return err
} }
common.CommandPropertySet("cron", appName, property, value, DefaultProperties, GlobalProperties) validProperties := DefaultProperties
globalProperties := GlobalProperties
if strings.HasPrefix(property, MaintenancePropertyPrefix) {
if appName == "--global" {
return fmt.Errorf("Task maintenance properties cannot be set globally")
}
cronTaskID := strings.TrimPrefix(property, MaintenancePropertyPrefix)
if cronTaskID == "" {
return fmt.Errorf("Invalid task maintenance property, missing ID")
}
tasks, err := FetchCronTasks(FetchCronTasksInput{AppName: appName})
if err != nil {
return err
}
for _, task := range tasks {
if task.ID == cronTaskID {
validProperties[property] = ""
globalProperties[property] = false
break
}
}
if _, ok := validProperties[property]; !ok {
return fmt.Errorf("Invalid task maintenance property, no matching task ID found: %s", property)
}
}
common.CommandPropertySet("cron", appName, property, value, validProperties, globalProperties)
scheduler := common.GetAppScheduler(appName) scheduler := common.GetAppScheduler(appName)
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{ _, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "scheduler-cron-write", Trigger: "scheduler-cron-write",
@@ -147,3 +199,8 @@ func CommandSet(appName string, property string, value string) error {
}) })
return err return err
} }
// CommandSuspend suspends a cron task
func CommandSuspend(appName string, cronID string) error {
return CommandSet(appName, fmt.Sprintf("%s%s", MaintenancePropertyPrefix, cronID), "true")
}

View File

@@ -47,7 +47,7 @@ trigger-docker-options-docker-args() {
*) *)
case "$IMAGE_SOURCE_TYPE" in case "$IMAGE_SOURCE_TYPE" in
dockerfile | nixpacks) dockerfile | nixpacks | railpack)
case "$line" in case "$line" in
--link* | -v* | --volume*) --link* | -v* | --volume*)
continue continue

View File

@@ -18,7 +18,7 @@ trigger-git-git-from-directory() {
trap "rm -rf '$TMP_WORK_DIR' '$TMP_WORK_DIR_2' >/dev/null" RETURN INT TERM EXIT trap "rm -rf '$TMP_WORK_DIR' '$TMP_WORK_DIR_2' >/dev/null" RETURN INT TERM EXIT
local has_code=false local has_code=false
if fn-git-cmd "$APP_ROOT" rev-parse "$DOKKU_DEPLOY_BRANCH" >/dev/null 2>&1; then if fn-git-cmd "$APP_ROOT" rev-parse "$DOKKU_DEPLOY_BRANCH" &>/dev/null; then
has_code=true has_code=true
fi fi

Some files were not shown because too many files have changed in this diff Show More