ci: deploy prebuilt images through dokploy api
This commit is contained in:
276
scripts/ci/dokploy_deploy_image.sh
Executable file
276
scripts/ci/dokploy_deploy_image.sh
Executable file
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
TARGET="${1:-${DOKPLOY_TARGET:-all}}"
|
||||
SUMMARY_FILE="${GITHUB_STEP_SUMMARY:-/dev/stdout}"
|
||||
DOKPLOY_API_URL="${DOKPLOY_API_URL:-https://deploy.dev.nii-ecos.ru/api}"
|
||||
|
||||
require_dokploy_api_token() {
|
||||
if [ -z "${DOKPLOY_API_TOKEN:-}" ]; then
|
||||
echo "DOKPLOY_API_TOKEN is required to deploy prebuilt images through Dokploy API" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
branch_tag() {
|
||||
local branch branch_tag sha_short
|
||||
|
||||
branch="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-branch}}"
|
||||
branch_tag="$(printf '%s' "${branch}" \
|
||||
| tr '[:upper:]' '[:lower:]' \
|
||||
| sed -E 's#[/[:space:]]+#-#g; s#[^a-z0-9_.-]+#-#g; s#^-+##; s#-+$##')"
|
||||
branch_tag="${branch_tag:-branch}"
|
||||
branch_tag="$(printf '%.120s' "${branch_tag}")"
|
||||
sha_short="$(printf '%s' "${GITHUB_SHA:-}" | cut -c1-7)"
|
||||
|
||||
if [ -n "${sha_short}" ]; then
|
||||
printf '%s-%s' "${branch_tag}" "${sha_short}"
|
||||
else
|
||||
printf '%s' "${branch_tag}"
|
||||
fi
|
||||
}
|
||||
|
||||
registry_username() {
|
||||
printf '%s' "${REGISTRY_USER:-${GITHUB_ACTOR:-}}"
|
||||
}
|
||||
|
||||
registry_password() {
|
||||
printf '%s' "${REGISTRY_PASSWORD:-${GITEA_TOKEN:-}}"
|
||||
}
|
||||
|
||||
require_registry_credentials() {
|
||||
if [ -z "$(registry_username)" ] || [ -z "$(registry_password)" ]; then
|
||||
echo "REGISTRY_USER/REGISTRY_TOKEN are required so Dokploy can pull private images" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
urlencode() {
|
||||
python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1]))' "$1"
|
||||
}
|
||||
|
||||
dokploy_get() {
|
||||
local endpoint="$1"
|
||||
|
||||
curl -fsS \
|
||||
--connect-timeout 5 \
|
||||
--max-time 30 \
|
||||
--retry 2 \
|
||||
--retry-delay 2 \
|
||||
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
|
||||
"${DOKPLOY_API_URL%/}/${endpoint}"
|
||||
}
|
||||
|
||||
dokploy_post() {
|
||||
local endpoint="$1"
|
||||
local payload="$2"
|
||||
|
||||
curl -fsS \
|
||||
--connect-timeout 5 \
|
||||
--max-time 60 \
|
||||
--retry 2 \
|
||||
--retry-delay 2 \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
|
||||
--data "${payload}" \
|
||||
"${DOKPLOY_API_URL%/}/${endpoint}"
|
||||
}
|
||||
|
||||
resolve_application_id() {
|
||||
local explicit_id="$1"
|
||||
local app_name="$2"
|
||||
local encoded response application_id
|
||||
|
||||
if [ -n "${explicit_id}" ]; then
|
||||
printf '%s' "${explicit_id}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -z "${app_name}" ]; then
|
||||
echo "Dokploy application id or appName is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
encoded="$(urlencode "${app_name}")"
|
||||
response="$(dokploy_get "application.search?appName=${encoded}&limit=10")"
|
||||
application_id="$(printf '%s' "${response}" | APP_NAME="${app_name}" python3 -c '
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
app_name = os.environ["APP_NAME"]
|
||||
payload = json.load(sys.stdin)
|
||||
|
||||
def walk(node):
|
||||
if isinstance(node, list):
|
||||
for item in node:
|
||||
yield from walk(item)
|
||||
return
|
||||
if not isinstance(node, dict):
|
||||
return
|
||||
if any(key in node for key in ("applicationId", "id", "appName", "name")):
|
||||
yield node
|
||||
for key in ("applications", "data", "items", "results"):
|
||||
if key in node:
|
||||
yield from walk(node[key])
|
||||
|
||||
candidates = list(walk(payload))
|
||||
matches = [
|
||||
item
|
||||
for item in candidates
|
||||
if item.get("appName") == app_name or item.get("name") == app_name
|
||||
]
|
||||
if not matches and len(candidates) == 1:
|
||||
matches = candidates
|
||||
if not matches:
|
||||
raise SystemExit(f"Application not found by appName={app_name!r}")
|
||||
identifier = matches[0].get("applicationId") or matches[0].get("id")
|
||||
if not identifier:
|
||||
raise SystemExit(f"Application id is missing for appName={app_name!r}")
|
||||
print(identifier)
|
||||
')"
|
||||
|
||||
printf '%s' "${application_id}"
|
||||
}
|
||||
|
||||
image_for_target() {
|
||||
local target="$1"
|
||||
local registry_path image_tag
|
||||
|
||||
registry_path="${REGISTRY_HOST}/${REGISTRY_NAMESPACE}"
|
||||
image_tag="$(branch_tag)"
|
||||
|
||||
case "${target}" in
|
||||
web)
|
||||
printf '%s/%s:%s' "${registry_path}" "${WEB_IMAGE}" "${image_tag}"
|
||||
;;
|
||||
worker | beat)
|
||||
printf '%s/%s:%s' "${registry_path}" "${CELERY_IMAGE}" "${image_tag}"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown Dokploy target: ${target}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
target_app_id_env() {
|
||||
case "$1" in
|
||||
web) printf '%s' "${DOKPLOY_DEV_WEB_APPLICATION_ID:-}" ;;
|
||||
worker) printf '%s' "${DOKPLOY_DEV_WORKER_APPLICATION_ID:-}" ;;
|
||||
beat) printf '%s' "${DOKPLOY_DEV_BEAT_APPLICATION_ID:-}" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
target_app_name_env() {
|
||||
case "$1" in
|
||||
web) printf '%s' "${DOKPLOY_DEV_WEB_APP_NAME:-service-backend-4mbxrs}" ;;
|
||||
worker) printf '%s' "${DOKPLOY_DEV_WORKER_APP_NAME:-service-backend-512y9c}" ;;
|
||||
beat) printf '%s' "${DOKPLOY_DEV_BEAT_APP_NAME:-service-backend-nvdyoq}" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
save_docker_provider() {
|
||||
local application_id="$1"
|
||||
local image="$2"
|
||||
local payload
|
||||
|
||||
payload="$(APPLICATION_ID="${application_id}" \
|
||||
DOCKER_IMAGE="${image}" \
|
||||
REGISTRY_USERNAME="$(registry_username)" \
|
||||
REGISTRY_PASSWORD_VALUE="$(registry_password)" \
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
|
||||
print(json.dumps({
|
||||
"applicationId": os.environ["APPLICATION_ID"],
|
||||
"dockerImage": os.environ["DOCKER_IMAGE"],
|
||||
"username": os.environ["REGISTRY_USERNAME"],
|
||||
"password": os.environ["REGISTRY_PASSWORD_VALUE"],
|
||||
"registryUrl": os.environ["REGISTRY_HOST"],
|
||||
}, ensure_ascii=True, separators=(",", ":")))
|
||||
PY
|
||||
)"
|
||||
|
||||
dokploy_post "application.saveDockerProvider" "${payload}" >/dev/null
|
||||
}
|
||||
|
||||
deploy_application() {
|
||||
local target="$1"
|
||||
local application_id="$2"
|
||||
local image="$3"
|
||||
local payload
|
||||
|
||||
payload="$(APPLICATION_ID="${application_id}" \
|
||||
DEPLOY_TARGET="${target}" \
|
||||
DOCKER_IMAGE="${image}" \
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
|
||||
target = os.environ["DEPLOY_TARGET"]
|
||||
image = os.environ["DOCKER_IMAGE"]
|
||||
sha = os.environ.get("GITHUB_SHA", "")
|
||||
|
||||
print(json.dumps({
|
||||
"applicationId": os.environ["APPLICATION_ID"],
|
||||
"title": f"CI deploy {target}",
|
||||
"description": f"{image} ({sha[:7]})" if sha else image,
|
||||
}, ensure_ascii=True, separators=(",", ":")))
|
||||
PY
|
||||
)"
|
||||
|
||||
dokploy_post "application.deploy" "${payload}" >/dev/null
|
||||
}
|
||||
|
||||
deploy_target() {
|
||||
local target="$1"
|
||||
local application_id image
|
||||
|
||||
application_id="$(resolve_application_id \
|
||||
"$(target_app_id_env "${target}")" \
|
||||
"$(target_app_name_env "${target}")")"
|
||||
image="$(image_for_target "${target}")"
|
||||
|
||||
echo "Dokploy ${target}: set Docker image ${image}"
|
||||
save_docker_provider "${application_id}" "${image}"
|
||||
deploy_application "${target}" "${application_id}" "${image}"
|
||||
|
||||
{
|
||||
echo "- ${target}: ${image}"
|
||||
} >>"${SUMMARY_FILE}"
|
||||
}
|
||||
|
||||
main() {
|
||||
require_dokploy_api_token
|
||||
require_registry_credentials
|
||||
|
||||
case "${TARGET}" in
|
||||
all | web | worker | celery | beat) ;;
|
||||
*)
|
||||
echo "dokploy target must be one of: all, web, worker, beat" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
{
|
||||
echo "Dokploy Docker-image deploy:"
|
||||
echo "Registry API: ${REGISTRY_API_URL:-${REGISTRY_HOST}}"
|
||||
} >>"${SUMMARY_FILE}"
|
||||
|
||||
if [ "${TARGET}" = "all" ] || [ "${TARGET}" = "web" ]; then
|
||||
deploy_target "web"
|
||||
fi
|
||||
if [ "${TARGET}" = "all" ] || [ "${TARGET}" = "worker" ] || [ "${TARGET}" = "celery" ]; then
|
||||
deploy_target "worker"
|
||||
fi
|
||||
if [ "${TARGET}" = "all" ] || [ "${TARGET}" = "beat" ]; then
|
||||
deploy_target "beat"
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
||||
Reference in New Issue
Block a user