208 lines
5.2 KiB
Bash
208 lines
5.2 KiB
Bash
#!/bin/bash
|
|
|
|
workspace_fail() {
|
|
echo "bootstrap_workspace.sh: $*" >&2
|
|
return 1
|
|
}
|
|
|
|
split_repository_slug() {
|
|
local repo_slug=$1
|
|
local owner repo_name
|
|
|
|
if [[ "${repo_slug}" != */* ]]; then
|
|
workspace_fail "repository slug must look like owner/repo: ${repo_slug}"
|
|
return 1
|
|
fi
|
|
|
|
owner=${repo_slug%%/*}
|
|
repo_name=${repo_slug#*/}
|
|
|
|
printf '%s\n%s\n' "${owner}" "${repo_name}"
|
|
}
|
|
|
|
sanitize_job_name() {
|
|
local value=${1:-job}
|
|
|
|
value=$(printf '%s' "${value}" | tr ' /:@' '-----')
|
|
value=$(printf '%s' "${value}" | tr -cd '[:alnum:]._-')
|
|
value=$(printf '%s' "${value}" | sed -E 's/-+/-/g; s/^-//; s/-$//')
|
|
|
|
if [ -z "${value}" ]; then
|
|
value="job"
|
|
fi
|
|
|
|
printf '%s\n' "${value}"
|
|
}
|
|
|
|
build_job_identity() {
|
|
local run_id=${1:-0}
|
|
local run_attempt=${2:-1}
|
|
local job_name=${3:-job}
|
|
|
|
printf '%s-%s-%s\n' "${run_id}" "${run_attempt}" "${job_name}"
|
|
}
|
|
|
|
build_mirror_path() {
|
|
local mirror_root=$1
|
|
local owner=$2
|
|
local repo_name=$3
|
|
|
|
printf '%s/%s/%s.git\n' "${mirror_root}" "${owner}" "${repo_name}"
|
|
}
|
|
|
|
build_job_workspace_root() {
|
|
local workspace_root=$1
|
|
local owner=$2
|
|
local repo_name=$3
|
|
local job_identity=$4
|
|
|
|
printf '%s/%s/%s/%s\n' "${workspace_root}" "${owner}" "${repo_name}" "${job_identity}"
|
|
}
|
|
|
|
build_job_repo_dir() {
|
|
local workspace_root=$1
|
|
local owner=$2
|
|
local repo_name=$3
|
|
local job_identity=$4
|
|
|
|
printf '%s/repo\n' "$(build_job_workspace_root "${workspace_root}" "${owner}" "${repo_name}" "${job_identity}")"
|
|
}
|
|
|
|
with_repo_lock() {
|
|
local lock_path=$1
|
|
shift
|
|
|
|
mkdir -p "$(dirname "${lock_path}")"
|
|
|
|
if ! command -v flock >/dev/null 2>&1; then
|
|
workspace_fail "flock is required for mirror synchronization"
|
|
return 1
|
|
fi
|
|
|
|
exec {lock_fd}>"${lock_path}"
|
|
flock "${lock_fd}"
|
|
"$@"
|
|
local status=$?
|
|
flock -u "${lock_fd}"
|
|
eval "exec ${lock_fd}>&-"
|
|
|
|
return "${status}"
|
|
}
|
|
|
|
ensure_bare_mirror_unlocked() {
|
|
local remote_url=$1
|
|
local mirror_path=$2
|
|
|
|
mkdir -p "$(dirname "${mirror_path}")"
|
|
|
|
if [ -d "${mirror_path}" ]; then
|
|
git -C "${mirror_path}" remote set-url origin "${remote_url}"
|
|
git -C "${mirror_path}" fetch --prune --prune-tags --tags origin
|
|
else
|
|
git clone --mirror "${remote_url}" "${mirror_path}" >/dev/null
|
|
fi
|
|
}
|
|
|
|
ensure_bare_mirror() {
|
|
local remote_url=$1
|
|
local mirror_path=$2
|
|
local lock_path
|
|
|
|
lock_path="${mirror_path}.lock"
|
|
with_repo_lock "${lock_path}" ensure_bare_mirror_unlocked "${remote_url}" "${mirror_path}"
|
|
}
|
|
|
|
create_job_workspace_from_mirror() {
|
|
local remote_url=$1
|
|
local mirror_path=$2
|
|
local job_workspace=$3
|
|
local repo_dir=$4
|
|
|
|
rm -rf "${job_workspace}"
|
|
mkdir -p "${job_workspace}"
|
|
git clone --shared "${mirror_path}" "${repo_dir}" >/dev/null
|
|
git -C "${repo_dir}" remote set-url origin "${remote_url}"
|
|
}
|
|
|
|
cleanup_job_workspace() {
|
|
local job_workspace=$1
|
|
|
|
if [ -z "${job_workspace}" ] || [ "${job_workspace}" = "/" ]; then
|
|
workspace_fail "refusing to remove invalid workspace path: ${job_workspace}"
|
|
return 1
|
|
fi
|
|
|
|
rm -rf "${job_workspace}"
|
|
}
|
|
|
|
prepare_job_workspace() {
|
|
local repo_slug=$1
|
|
local remote_url=$2
|
|
local mirror_root=$3
|
|
local workspace_root=$4
|
|
local run_id=$5
|
|
local run_attempt=${6:-1}
|
|
local job_name=${7:-job}
|
|
local owner repo_name safe_job_name job_identity mirror_path job_workspace repo_dir
|
|
local slug_parts
|
|
|
|
mapfile -t slug_parts < <(split_repository_slug "${repo_slug}")
|
|
owner=${slug_parts[0]}
|
|
repo_name=${slug_parts[1]}
|
|
safe_job_name=$(sanitize_job_name "${job_name}")
|
|
job_identity=$(build_job_identity "${run_id}" "${run_attempt}" "${safe_job_name}")
|
|
mirror_path=$(build_mirror_path "${mirror_root}" "${owner}" "${repo_name}")
|
|
job_workspace=$(build_job_workspace_root "${workspace_root}" "${owner}" "${repo_name}" "${job_identity}")
|
|
repo_dir=$(build_job_repo_dir "${workspace_root}" "${owner}" "${repo_name}" "${job_identity}")
|
|
|
|
ensure_bare_mirror "${remote_url}" "${mirror_path}"
|
|
create_job_workspace_from_mirror "${remote_url}" "${mirror_path}" "${job_workspace}" "${repo_dir}"
|
|
|
|
printf 'REPO_OWNER=%s\n' "${owner}"
|
|
printf 'REPO_NAME=%s\n' "${repo_name}"
|
|
printf 'JOB_IDENTITY=%s\n' "${job_identity}"
|
|
printf 'MIRROR_PATH=%s\n' "${mirror_path}"
|
|
printf 'JOB_WORKSPACE=%s\n' "${job_workspace}"
|
|
printf 'REPO_DIR=%s\n' "${repo_dir}"
|
|
}
|
|
|
|
print_usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
bootstrap_workspace.sh prepare-job-workspace <repo-slug> <remote-url> <mirror-root> <workspace-root> <run-id> <run-attempt> <job-name>
|
|
bootstrap_workspace.sh cleanup-job-workspace <job-workspace>
|
|
EOF
|
|
}
|
|
|
|
main() {
|
|
local command=${1:-}
|
|
|
|
case "${command}" in
|
|
prepare-job-workspace)
|
|
[ $# -eq 8 ] || {
|
|
print_usage
|
|
return 1
|
|
}
|
|
shift
|
|
prepare_job_workspace "$@"
|
|
;;
|
|
cleanup-job-workspace)
|
|
[ $# -eq 2 ] || {
|
|
print_usage
|
|
return 1
|
|
}
|
|
shift
|
|
cleanup_job_workspace "$1"
|
|
;;
|
|
*)
|
|
print_usage
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
set -euo pipefail
|
|
main "$@"
|
|
fi
|