Files
dokku/plugins/git/internal-functions
Jose Diaz-Gonzalez ef25a9b0d8 refactor: standardize on shorthand for redirecting all output to /dev/null
The shorthand is more prevalent in this codebase, and is something that bash supports, so we should just use the same thing everywhere.

Note that we do not use shorthand redirect in Makefile as shell parsing is a bit different in Make and the shorthand redirect doesn't seem to be properly supported, causing CI errors.
2023-08-05 10:58:57 -04:00

584 lines
17 KiB
Bash
Executable File

#!/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-git-allow-host() {
declare desc="adds a host to known_hosts"
local cmd="git:allow-host"
[[ "$1" == "$cmd" ]] && shift 1
declare HOST="$1"
[[ -z "$HOST" ]] && dokku_log_fail "Please supply a git host"
ssh-keyscan -t rsa "$HOST" >>"$DOKKU_ROOT/.ssh/known_hosts"
dokku_log_info1 "$HOST added to known hosts"
}
cmd-git-from-archive() {
declare desc="updates an app's git repository with a given archive file"
local cmd="git:from-archive"
[[ "$1" == "$cmd" ]] && shift 1
declare APP ARCHIVE_URL USER_NAME USER_EMAIL
local ARCHIVE_TYPE="tar"
ARGS=()
skip=false
for arg in "$@"; do
if [[ "$arg" == "--archive-type" ]]; then
skip=true
continue
fi
if [[ "$skip" == "true" ]]; then
ARCHIVE_TYPE="$arg"
skip=false
continue
fi
ARGS+=("$arg")
done
APP="${ARGS[0]}"
ARCHIVE_URL="${ARGS[1]}"
USER_NAME="${ARGS[2]}"
USER_EMAIL="${ARGS[3]}"
verify_app_name "$APP"
[[ -z "$ARCHIVE_URL" ]] && dokku_log_fail "Please specify an archive url or -- to fetch the archive from stdin"
local VALID_ARCHIVE_TYPES=("tar" "tar.gz" "zip")
if ! fn-in-array "$ARCHIVE_TYPE" "${ARCHIVE_TYPE[@]}"; then
dokku_log_fail "Invalid archive type specified, valid archive types include: tar, tar.gz, zip"
fi
plugn trigger git-from-archive "$APP" "$ARCHIVE_URL" "$ARCHIVE_TYPE" "$USER_NAME" "$USER_EMAIL"
plugn trigger deploy-source-set "$APP" "$ARCHIVE_TYPE" "$ARCHIVE_URL"
}
cmd-git-auth() {
declare desc="configures netrc authentication for a given git server"
local cmd="git:auth"
[[ "$1" == "$cmd" ]] && shift 1
declare HOST="$1" USERNAME="$2" PASSWORD="$3"
[[ -z "$HOST" ]] && dokku_log_fail "Please supply a git host"
if [[ -n "$USERNAME" ]] && [[ -z "$PASSWORD" ]]; then
dokku_log_fail "Missing password for netrc auth entry"
fi
if [[ -z "$USERNAME" ]]; then
dokku_log_info1 "Removing netrc auth entry for host $HOST"
netrc unset "$HOST"
return $?
fi
dokku_log_info1 "Setting netrc auth entry for host $HOST"
touch "${DOKKU_ROOT}/.netrc"
netrc set "$HOST" "$USERNAME" "$PASSWORD"
}
cmd-git-load-image() {
declare desc="updates an app's git repository with a docker image loaded from stdin"
local cmd="git:load-image"
[[ "$1" == "$cmd" ]] && shift 1
declare APP DOCKER_IMAGE USER_NAME USER_EMAIL
local BUILD_DIR
ARGS=()
skip=false
for arg in "$@"; do
if [[ "$arg" == "--build-dir" ]]; then
skip=true
continue
fi
if [[ "$skip" == "true" ]]; then
BUILD_DIR="$arg"
skip=false
continue
fi
ARGS+=("$arg")
done
APP="${ARGS[0]}"
DOCKER_IMAGE="${ARGS[1]}"
USER_NAME="${ARGS[2]}"
USER_EMAIL="${ARGS[3]}"
verify_app_name "$APP"
[[ -z "$DOCKER_IMAGE" ]] && dokku_log_fail "Please specify a docker image"
[[ ! -t 0 ]] || dokku_log_fail "Expecting tar archive containing docker image on STDIN"
cat | docker load
if ! verify_image "$DOCKER_IMAGE"; then
dokku_log_fail "Loaded image tarball but the specified docker image was not found: $DOCKER_IMAGE"
fi
if ! plugn trigger git-from-image "$APP" "$DOCKER_IMAGE" "$BUILD_DIR" "$USER_NAME" "$USER_EMAIL"; then
return 1
fi
plugn trigger deploy-source-set "$APP" "docker-image" "$DOCKER_IMAGE"
}
cmd-git-from-image() {
declare desc="updates an app's git repository with a given docker image"
local cmd="git:from-image"
[[ "$1" == "$cmd" ]] && shift 1
declare APP DOCKER_IMAGE USER_NAME USER_EMAIL
local BUILD_DIR
ARGS=()
skip=false
for arg in "$@"; do
if [[ "$arg" == "--build-dir" ]]; then
skip=true
continue
fi
if [[ "$skip" == "true" ]]; then
BUILD_DIR="$arg"
skip=false
continue
fi
ARGS+=("$arg")
done
APP="${ARGS[0]}"
DOCKER_IMAGE="${ARGS[1]}"
USER_NAME="${ARGS[2]}"
USER_EMAIL="${ARGS[3]}"
verify_app_name "$APP"
[[ -z "$DOCKER_IMAGE" ]] && dokku_log_fail "Please specify a docker image"
if ! plugn trigger git-from-image "$APP" "$DOCKER_IMAGE" "$BUILD_DIR" "$USER_NAME" "$USER_EMAIL"; then
return 1
fi
plugn trigger deploy-source-set "$APP" "docker-image" "$DOCKER_IMAGE"
}
cmd-git-sync() {
declare desc="clone or fetch an app from remote git repo"
local cmd="git:sync"
[[ "$1" == "$cmd" ]] && shift 1
declare APP GIT_REMOTE GIT_REF FLAG
ARGS=()
for arg in "$@"; do
if [[ "$arg" == "--build" ]]; then
FLAG="--build"
continue
fi
ARGS+=("$arg")
done
APP="${ARGS[0]}"
GIT_REMOTE="${ARGS[1]}"
GIT_REF="${ARGS[2]}"
verify_app_name "$APP"
if [[ -d "$DOKKU_LIB_ROOT/data/git/$APP" ]]; then
if has_tty eq 0; then
cmd-git-unlock "$APP"
if [[ -d "$DOKKU_LIB_ROOT/data/git/$APP" ]]; then
dokku_log_fail "Failed to delete existing clone folder"
exit 15
fi
else
dokku_log_fail "Run 'git:unlock' to remove the existing temporary clone"
fi
fi
if [[ -z "$GIT_REMOTE" ]]; then
dokku_log_fail "Missing GIT_REMOTE parameter"
return
fi
local APP_ROOT="$DOKKU_ROOT/$APP"
if [[ "$(fn-git-cmd "$APP_ROOT" count-objects)" == "0 objects, 0 kilobytes" ]]; then
dokku_log_info1_quiet "Cloning $APP from $GIT_REMOTE#$GIT_REF"
fn-git-clone "$APP" "$GIT_REMOTE" "$GIT_REF"
else
fn-git-fetch "$APP" "$GIT_REMOTE" "$GIT_REF"
fi
if [[ "$FLAG" == "--build" ]]; then
if [[ -n "$GIT_REF" ]]; then
GIT_REF="$(fn-git-cmd "$APP_ROOT" rev-parse "$GIT_REF")"
plugn trigger receive-app "$APP" "$GIT_REF"
else
plugn trigger receive-app "$APP"
fi
plugn trigger deploy-source-set "$APP" "git-sync" "${GIT_REMOTE}#${GIT_REF}"
fi
}
cmd-git-unlock() {
declare desc="removes the clone folder for an app"
local cmd="git:unlock"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1" FLAG
for arg in "$@"; do
if [[ "$arg" == "--force" ]]; then
FLAG="--force"
continue
fi
ARGS+=("$arg")
done
local APP_CLONE_ROOT="$DOKKU_LIB_ROOT/data/git/$APP"
if [[ -d "$APP_CLONE_ROOT" ]]; then
if [[ "$FLAG" == "--force" ]]; then
fn-git-remove-clone-folder "$APP_CLONE_ROOT"
else
read -rp "Are you sure that want to delete clone folder (y/n)?" choice
case "$choice" in
y | Y)
fn-git-remove-clone-folder "$APP_CLONE_ROOT"
;;
n | N) echo "no" ;;
*) echo "please answer with yes or no" ;;
esac
fi
else
dokku_log_info1 "No clone folder exists app already unlocked"
fi
}
cmd-git-public-key() {
declare desc="outputs the dokku public deploy key"
local cmd="git:public-key"
[[ "$1" == "$cmd" ]] && shift 1
if [[ -f "$DOKKU_ROOT/.ssh/id_ed25519.pub" ]]; then
cat "$DOKKU_ROOT/.ssh/id_ed25519.pub"
return
elif [[ -f "$DOKKU_ROOT/.ssh/id_rsa.pub" ]]; then
cat "$DOKKU_ROOT/.ssh/id_rsa.pub"
return
fi
fn-git-auth-error
}
cmd-git-report() {
declare desc="displays a git report for one or more apps"
declare cmd="git: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-git-report-single "$app" "$INFO_FLAG" | tee || true
done
else
cmd-git-report-single "$APP" "$INFO_FLAG"
fi
}
cmd-git-report-single() {
declare APP="$1" INFO_FLAG="$2"
local APP_ROOT="$DOKKU_ROOT/$APP"
if [[ "$INFO_FLAG" == "true" ]]; then
INFO_FLAG=""
fi
verify_app_name "$APP"
local flag_map=(
"--git-deploy-branch: $(fn-plugin-property-get "git" "$APP" "deploy-branch" "master")"
"--git-global-deploy-branch: $(fn-plugin-property-get "git" "--global" "deploy-branch" "master")"
"--git-keep-git-dir: $(fn-plugin-property-get "git" "$APP" "keep-git-dir" "false")"
"--git-rev-env-var: $(fn-plugin-property-get "git" "$APP" "rev-env-var" "GIT_REV")"
"--git-sha: $(fn-git-cmd "$APP_ROOT" rev-parse --short HEAD 2>/dev/null || false)"
"--git-source-image: $(fn-git-source-image "$APP")"
"--git-last-updated-at: $(fn-git-last-updated-at "$APP")"
)
if [[ -z "$INFO_FLAG" ]]; then
dokku_log_info2_quiet "${APP} git 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
}
cmd-git-status() {
declare desc="check the git status output for an app"
local cmd="git:status"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1"
local APP_ROOT="$DOKKU_ROOT/$APP"
verify_app_name "$APP"
fn-git-cmd "$APP_ROOT" status
}
fn-git-auth-error() {
dokku_log_warn "There is no deploy key associated with the $DOKKU_SYSTEM_USER user."
dokku_log_warn "Generate an ssh key with the following command (do not specify a password):"
dokku_log_warn " ssh-keygen -t ed25519 -C 'example@example.com'"
dokku_log_fail "As an alternative, configure the netrc authentication via the git:auth command"
}
fn-git-remove-clone-folder() {
declare APP_CLONE_ROOT="$1"
dokku_log_info1 "About to delete $APP_CLONE_ROOT"
rm -rf "$APP_CLONE_ROOT"
dokku_log_info1 "Successfully deleted $APP_CLONE_ROOT"
}
fn-git-create-hook() {
declare APP="$1"
local APP_PATH="$DOKKU_ROOT/$APP"
local PRERECEIVE_HOOK="$APP_PATH/hooks/pre-receive"
if [[ ! -d "$APP_PATH/refs" ]]; then
suppress_output git init -q --bare "$APP_PATH"
fi
cat >"$PRERECEIVE_HOOK" <<EOF
#!/usr/bin/env bash
set -e
set -o pipefail
cat | DOKKU_ROOT="$DOKKU_ROOT" dokku git-hook $APP
EOF
chmod +x "$PRERECEIVE_HOOK"
}
fn-git-clone() {
declare desc="creates an app from remote git repo"
declare APP="$1" GIT_REMOTE="$2" GIT_REF="$3"
local APP_CLONE_ROOT="$DOKKU_LIB_ROOT/data/git/$APP"
local APP_ROOT="$DOKKU_ROOT/$APP"
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
if [[ "$(fn-git-cmd "$APP_ROOT" count-objects)" != "0 objects, 0 kilobytes" ]]; then
dokku_log_fail "The clone subcommand can only be executed for new applications"
fi
if [[ -z "$GIT_REF" ]]; then
GIT_REF="$(fn-git-deploy-branch "$APP")"
fi
trap "rm -rf '$APP_CLONE_ROOT' > /dev/null" RETURN INT TERM EXIT
is_ref=true
if GIT_TERMINAL_PROMPT=0 git clone --depth 1 -n --branch "$GIT_REF" "$GIT_REMOTE" "$APP_CLONE_ROOT" 2>/dev/null; then
is_ref=false
fi
rm -rf "$APP_CLONE_ROOT"
if [[ "$is_ref" == "true" ]]; then
GIT_TERMINAL_PROMPT=0 suppress_output git clone -n "$GIT_REMOTE" "$APP_CLONE_ROOT"
fn-git-cmd "$APP_CLONE_ROOT" checkout -qq "$GIT_REF"
else
GIT_TERMINAL_PROMPT=0 suppress_output git clone -n --branch "$GIT_REF" "$GIT_REMOTE" "$APP_CLONE_ROOT"
if fn-git-cmd "$APP_CLONE_ROOT" show-ref --verify "refs/heads/$GIT_REF" &>/dev/null; then
dokku_log_verbose "Detected branch, setting deploy-branch to $GIT_REF"
fn-plugin-property-write "git" "$APP" "deploy-branch" "$GIT_REF"
fi
fi
DOKKU_DEPLOY_BRANCH="$(fn-git-deploy-branch "$APP")"
if [[ "$GIT_REF" != "$DOKKU_DEPLOY_BRANCH" ]]; then
if [[ "$is_ref" == "true" ]]; then
fn-git-cmd "$APP_CLONE_ROOT" branch -qq -D "$DOKKU_DEPLOY_BRANCH" 2>/dev/null || true
fi
fn-git-cmd "$APP_CLONE_ROOT" checkout -qq -b "$DOKKU_DEPLOY_BRANCH"
fi
rsync -a "$APP_CLONE_ROOT/.git/" "$APP_ROOT"
fn-git-create-hook "$APP"
fn-git-cmd "$APP_ROOT" config --add core.bare true
}
fn-git-cmd() {
declare GIT_DIR="$1"
local exit_code="1"
shift 1
pushd "$GIT_DIR" >/dev/null
git "$@"
exit_code="$?"
popd &>/dev/null || pushd "/tmp" >/dev/null
return $exit_code
}
fn-git-fetch() {
declare APP="$1" GIT_REMOTE="$2" GIT_REF="$3"
local DOKKU_DEPLOY_BRANCH
local APP_ROOT="$DOKKU_ROOT/$APP"
if [[ "$(fn-git-cmd "$APP_ROOT" count-objects)" == "0 objects, 0 kilobytes" ]]; then
dokku_log_fail "The fetch subcommand can only be executed for existing applications"
fi
DOKKU_DEPLOY_BRANCH="$(fn-git-deploy-branch "$APP")"
if ! fn-git-cmd "$APP_ROOT" check-ref-format --branch "$DOKKU_DEPLOY_BRANCH" &>/dev/null; then
dokku_log_warn "Invalid branch name '$DOKKU_DEPLOY_BRANCH' specified via deploy-branch setting."
dokku_log_warn "For more details, please see the man page for 'git-check-ref-format.'"
return
fi
fn-git-cmd "$APP_ROOT" remote rm remote &>/dev/null || true
fn-git-cmd "$APP_ROOT" remote add --mirror=fetch --no-tags remote "$GIT_REMOTE"
if [[ -z "$GIT_REF" ]]; then
GIT_TERMINAL_PROMPT=0 fn-git-cmd "$APP_ROOT" fetch --update-head-ok remote "$DOKKU_DEPLOY_BRANCH"
else
GIT_TERMINAL_PROMPT=0 fn-git-cmd "$APP_ROOT" fetch --update-head-ok remote
fn-git-cmd "$APP_ROOT" update-ref "refs/heads/$DOKKU_DEPLOY_BRANCH" "$GIT_REF"
fi
fn-git-cmd "$APP_ROOT" config --add core.bare true
}
fn-git-has-code() {
declare desc="checks if there is code at a specific branch"
declare APP="$1" DOKKU_DEPLOY_BRANCH="$2"
if [[ -z "$DOKKU_DEPLOY_BRANCH" ]]; then
DOKKU_DEPLOY_BRANCH="$(fn-git-deploy-branch "$APP")"
fi
local HEAD_FILE="$DOKKU_ROOT/$APP/refs/heads/$DOKKU_DEPLOY_BRANCH"
if [[ -f "$HEAD_FILE" ]]; then
return 0
fi
return 1
}
fn-git-last-updated-at() {
declare desc="retrieve the deploy branch for a given application"
declare APP="$1"
local DOKKU_DEPLOY_BRANCH="$(fn-git-deploy-branch "$APP")"
local HEAD_FILE="$DOKKU_ROOT/$APP/refs/heads/$DOKKU_DEPLOY_BRANCH"
if [[ -f "$HEAD_FILE" ]]; then
stat -c %Y "$HEAD_FILE"
fi
}
fn-git-deploy-branch() {
declare desc="retrieve the deploy branch for a given application"
local APP="$1"
local DEFAULT_BRANCH="${2-master}"
local DOKKU_DEPLOY_BRANCH="$(fn-plugin-property-get "git" "$APP" "deploy-branch" "")"
local DOKKU_GLOBAL_DEPLOY_BRANCH="$(fn-plugin-property-get "git" "--global" "deploy-branch" "")"
if [[ -n "$DOKKU_DEPLOY_BRANCH" ]]; then
echo "$DOKKU_DEPLOY_BRANCH"
elif [[ -n "$DOKKU_GLOBAL_DEPLOY_BRANCH" ]]; then
echo "$DOKKU_GLOBAL_DEPLOY_BRANCH"
else
echo "$DEFAULT_BRANCH"
fi
}
fn-git-setup-build-dir() {
declare APP="$1" GIT_WORKDIR="$2" REV="$3"
local DOKKU_KEEP_GIT_DIR="$(fn-plugin-property-get "git" "$APP" "keep-git-dir" "")"
if [[ "$DOKKU_KEEP_GIT_DIR" == "true" ]]; then
fn-git-setup-build-dir-new "$APP" "$GIT_WORKDIR" "$REV"
else
fn-git-setup-build-dir-worktree "$APP" "$GIT_WORKDIR" "$REV"
fi
}
fn-git-setup-build-dir-new() {
declare APP="$1" GIT_WORKDIR="$2" REV="$3"
local APP_ROOT="$DOKKU_ROOT/$APP"
local DOKKU_DEPLOY_BRANCH="$(fn-git-deploy-branch "$APP")"
# unset the git quarantine path to allow us to use 2.13.0+
# See this issue for more information: https://github.com/dokku/dokku/issues/2796
unset GIT_QUARANTINE_PATH
# repo workaround from https://gitlab.com/gitlab-org/gitlab-foss/-/issues/52249
# and https://docs.gitlab.com/ee/user/project/repository/repository_mirroring.html#preventing-conflicts-using-a-pre-receive-hook
fn-git-cmd "$APP_ROOT" archive "$REV" | tar x --warning=none -C "${GIT_WORKDIR}"
suppress_output fn-git-cmd "$GIT_WORKDIR" clone --bare "$DOKKU_ROOT/$APP" .git
suppress_output fn-git-cmd "$GIT_WORKDIR" config --local gc.auto 0
suppress_output fn-git-cmd "$GIT_WORKDIR" config --local --bool core.bare false
suppress_output fn-git-cmd "$GIT_WORKDIR" config --local receive.denyCurrentBranch ignore
suppress_output fn-git-cmd "$GIT_WORKDIR" symbolic-ref HEAD "refs/heads/$DOKKU_DEPLOY_BRANCH"
suppress_output fn-git-cmd "$GIT_WORKDIR" reset "$REV" -- .
suppress_output fn-git-cmd "$GIT_WORKDIR" push $GIT_WORKDIR $REV:refs/heads/$DOKKU_DEPLOY_BRANCH
fn-git-setup-build-dir-submodules "$APP" "$GIT_WORKDIR"
}
fn-git-setup-build-dir-worktree() {
declare APP="$1" GIT_WORKDIR="$2" REV="$3"
local APP_ROOT="$DOKKU_ROOT/$APP"
# unset the git quarantine path to allow us to use 2.13.0+
# See this issue for more information: https://github.com/dokku/dokku/issues/2796
unset GIT_QUARANTINE_PATH
suppress_output fn-git-cmd "$APP_ROOT" worktree add "$GIT_WORKDIR" "$REV"
fn-git-setup-build-dir-submodules "$APP" "$GIT_WORKDIR"
suppress_output fn-git-cmd "$APP_ROOT" worktree prune
}
fn-git-setup-build-dir-submodules() {
declare APP="$1" GIT_WORKDIR="$2"
local DOKKU_KEEP_GIT_DIR="$(fn-plugin-property-get "git" "$APP" "keep-git-dir" "")"
# unset the git quarantine path to allow us to use 2.13.0+
# See this issue for more information: https://github.com/dokku/dokku/issues/2796
unset GIT_QUARANTINE_PATH
suppress_output fn-git-cmd "$GIT_WORKDIR" submodule sync --recursive
suppress_output fn-git-cmd "$GIT_WORKDIR" submodule update --init --recursive
if [[ "$DOKKU_KEEP_GIT_DIR" != "true" ]]; then
pushd "$GIT_WORKDIR" >/dev/null
find "$GIT_WORKDIR" -name .git -prune -exec rm -rf {} \; >/dev/null
popd &>/dev/null || pushd "/tmp" >/dev/null
fi
}
fn-git-source-image() {
declare desc="retrieve the source-image for a given application"
declare APP="$1"
fn-plugin-property-get "git" "$APP" "source-image" ""
}