#!/usr/bin/env bash # Trigger a BuildKit build via the docker-build-container HTTP API. # Requires: bash, curl, mktemp, tee, awk, grep (no Python). # # Required env: # BUILDKIT_URL POST endpoint (e.g. https://buildkit.example/build) # BUILDKIT_TOKEN JWT from mint_token.py # TAGS Newline- or comma-separated image refs # USERNAME Registry username # PASSWORD Registry password # # Context — provide one or both: # COMMIT Git commit SHA; required for JSON (no FILES) # FILES Newline-separated paths to upload (multipart). Each path is # both the local file and the context path (multipart filename). # # Optional: # REMOTE Registry host for auth (defaults on the server to Docker Hub) # REPO_USERNAME Git username for private repo clone # REPO_PASSWORD Git password/token for private repo clone set -euo pipefail json_string() { local s=$1 i c o out= for ((i = 0; i < ${#s}; i++)); do c=${s:i:1} # case patterns are globs: quote "\\" so a real backslash matches. case "$c" in '"') out+='\"' ;; "\\") out+='\\' ;; $'\n') out+='\n' ;; $'\r') out+='\r' ;; $'\t') out+='\t' ;; *) printf -v o '%d' "'$c" if ((o < 32)); then printf -v out '%s\\u%04x' "$out" "$o" else out+="$c" fi ;; esac done printf '"%s"' "$out" } : "${BUILDKIT_URL:?BUILDKIT_URL is required}" : "${BUILDKIT_TOKEN:?BUILDKIT_TOKEN is required}" : "${TAGS:?TAGS is required}" : "${USERNAME:?USERNAME is required}" : "${PASSWORD:?PASSWORD is required}" tag_list=() while IFS= read -r tag; do [ -z "${tag}" ] && continue tag_list+=("${tag}") done < <(printf '%s\n' "${TAGS}" | tr ',' '\n') tags_json='[' sep= for tag in "${tag_list[@]}"; do tags_json+="${sep}$(json_string "${tag}")" sep=, done tags_json+=']' file_specs=() if [ -n "${FILES:-}" ]; then while IFS= read -r entry; do [ -z "${entry}" ] && continue file_specs+=("${entry}") done < <(printf '%s\n' "${FILES}") fi tmpdir="$(mktemp -d)" trap 'rm -rf "${tmpdir}"' EXIT headers_file="${tmpdir}/headers" response_file="${tmpdir}/response" curl_args=( -sSN -D "${headers_file}" -X POST "${BUILDKIT_URL}" ) if [ "${#file_specs[@]}" -gt 0 ]; then # Multipart: --form-string for fields (literal), -F for file parts. curl_args+=( --form-string "token=${BUILDKIT_TOKEN}" --form-string "tags=${tags_json}" --form-string "username=${USERNAME}" --form-string "password=${PASSWORD}" ) if [ -n "${COMMIT:-}" ]; then curl_args+=(--form-string "commit=${COMMIT}") fi if [ -n "${REMOTE:-}" ]; then curl_args+=(--form-string "remote=${REMOTE}") fi if [ -n "${REPO_USERNAME:-}" ]; then curl_args+=(--form-string "repo_username=${REPO_USERNAME}") fi if [ -n "${REPO_PASSWORD:-}" ]; then curl_args+=(--form-string "repo_password=${REPO_PASSWORD}") fi for path in "${file_specs[@]}"; do # filename= is the relative path inside the build context. curl_args+=(-F "files=@${path};filename=${path}") done else body_file="${tmpdir}/body.json" { printf '{"token":%s,"commit":%s,"tags":%s,"username":%s,"password":%s' \ "$(json_string "${BUILDKIT_TOKEN}")" \ "$(json_string "${COMMIT:-}")" \ "${tags_json}" \ "$(json_string "${USERNAME}")" \ "$(json_string "${PASSWORD}")" if [ -n "${REMOTE:-}" ]; then printf ',"remote":%s' "$(json_string "${REMOTE}")" fi if [ -n "${REPO_USERNAME:-}" ]; then printf ',"repo_username":%s' "$(json_string "${REPO_USERNAME}")" fi if [ -n "${REPO_PASSWORD:-}" ]; then printf ',"repo_password":%s' "$(json_string "${REPO_PASSWORD}")" fi printf '}' } >"${body_file}" curl_args+=( -H "Content-Type: application/json" --data-binary @"${body_file}" ) fi curl "${curl_args[@]}" | tee "${response_file}" http_code="$(awk '/^HTTP\//{code=$2} END{print code}' "${headers_file}")" if [ "${http_code}" != "200" ]; then echo "buildkit returned HTTP ${http_code:-?} (expected 200)" >&2 exit 1 fi if grep -q '\[build-service\] ERROR:' "${response_file}"; then echo "buildkit reported a build failure" >&2 exit 1 fi