143 lines
4.2 KiB
Bash
Executable File
143 lines
4.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
fail() {
|
|
printf 'release-service client: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
require_safe_value() {
|
|
local name="$1"
|
|
local value="$2"
|
|
local pattern="$3"
|
|
|
|
[[ -n "${value}" ]] || fail "${name} is required"
|
|
[[ "${value}" =~ ${pattern} ]] || fail "${name} contains unsupported characters"
|
|
}
|
|
|
|
decode_required_file() {
|
|
local output_path="$1"
|
|
local encoded_value="$2"
|
|
local description="$3"
|
|
|
|
[[ -n "${encoded_value}" ]] || fail "${description} base64 secret is required"
|
|
printf '%s' "${encoded_value}" | base64 -d >"${output_path}" || fail \
|
|
"${description} base64 secret is invalid"
|
|
[[ -s "${output_path}" ]] || fail "${description} decoded to an empty file"
|
|
}
|
|
|
|
[[ $# -ge 3 ]] || fail \
|
|
"usage: release-service.sh <service-group> <run-id> <exact-image-ref...> [--refresh-data]"
|
|
|
|
service_group="$1"
|
|
run_id="$2"
|
|
shift 2
|
|
|
|
refresh_data=false
|
|
if [[ "${!#}" == "--refresh-data" ]]; then
|
|
refresh_data=true
|
|
set -- "${@:1:$(($# - 1))}"
|
|
fi
|
|
|
|
case "${service_group}" in
|
|
state-corp-backend)
|
|
expected_refs=2
|
|
;;
|
|
state-corp-frontend)
|
|
expected_refs=1
|
|
;;
|
|
*)
|
|
fail "unsupported service group: ${service_group}"
|
|
;;
|
|
esac
|
|
|
|
[[ $# -eq ${expected_refs} ]] || fail \
|
|
"${service_group} requires exactly ${expected_refs} image reference(s)"
|
|
require_safe_value "run id" "${run_id}" '^[A-Za-z0-9._:-]+$'
|
|
|
|
release_host="${RELEASE_HOST:-}"
|
|
release_user="${RELEASE_USER:-}"
|
|
release_proxy="${RELEASE_PROXY:-}"
|
|
release_service_path="${RELEASE_SERVICE_PATH:-}"
|
|
|
|
require_safe_value "RELEASE_HOST" "${release_host}" '^[A-Za-z0-9._:-]+$'
|
|
require_safe_value "RELEASE_USER" "${release_user}" '^[A-Za-z0-9._-]+$'
|
|
require_safe_value \
|
|
"RELEASE_SERVICE_PATH" "${release_service_path}" '^/[A-Za-z0-9._/-]+$'
|
|
case "${release_service_path}" in
|
|
/opt/ecos-dev/release-service)
|
|
image_ref_pattern='^[A-Za-z0-9._:/-]+@sha256:[a-f0-9]{64}$'
|
|
[[ "${refresh_data}" == false ]] || fail "dev releases cannot refresh data"
|
|
;;
|
|
/opt/ecos-main/release-service)
|
|
image_ref_pattern='^[A-Za-z0-9._:/-]+@sha256:[a-f0-9]{64}$'
|
|
;;
|
|
/ecos/release-service)
|
|
image_ref_pattern='^[A-Za-z0-9._:/-]+@sha256:[a-f0-9]{64}$'
|
|
[[ "${refresh_data}" == false ]] || fail "customer releases cannot refresh data"
|
|
;;
|
|
*)
|
|
fail "unsupported RELEASE_SERVICE_PATH"
|
|
;;
|
|
esac
|
|
if [[ "${refresh_data}" == true && "${service_group}" != state-corp-backend ]]; then
|
|
fail "only an internal-main backend release can refresh data"
|
|
fi
|
|
image_refs=("$@")
|
|
for image_ref in "${image_refs[@]}"; do
|
|
[[ "${image_ref}" =~ ${image_ref_pattern} ]] || fail \
|
|
"image reference does not match the selected release target contract"
|
|
done
|
|
if [[ -n "${release_proxy}" ]]; then
|
|
require_safe_value "RELEASE_PROXY" "${release_proxy}" '^[A-Za-z0-9._@:-]+$'
|
|
fi
|
|
|
|
ssh_tmp_dir="$(mktemp -d)"
|
|
trap 'rm -rf "${ssh_tmp_dir}"' EXIT
|
|
key_path="${ssh_tmp_dir}/deploy-key"
|
|
known_hosts_path="${ssh_tmp_dir}/known-hosts"
|
|
|
|
decode_required_file \
|
|
"${key_path}" \
|
|
"${RELEASE_SSH_KEY_B64:-}" \
|
|
"deploy SSH key"
|
|
decode_required_file \
|
|
"${known_hosts_path}" \
|
|
"${RELEASE_KNOWN_HOSTS_B64:-}" \
|
|
"pinned SSH known_hosts"
|
|
chmod 0600 "${key_path}" "${known_hosts_path}"
|
|
|
|
ssh_options=(
|
|
-i "${key_path}"
|
|
-o BatchMode=yes
|
|
-o ConnectTimeout=15
|
|
-o GlobalKnownHostsFile=/dev/null
|
|
-o IdentitiesOnly=yes
|
|
-o LogLevel=ERROR
|
|
-o StrictHostKeyChecking=yes
|
|
-o "UserKnownHostsFile=${known_hosts_path}"
|
|
)
|
|
|
|
if [[ -n "${release_proxy}" ]]; then
|
|
proxy_command="ssh -i ${key_path} -o BatchMode=yes -o ConnectTimeout=15"
|
|
proxy_command+=" -o GlobalKnownHostsFile=/dev/null -o IdentitiesOnly=yes"
|
|
proxy_command+=" -o LogLevel=ERROR -o StrictHostKeyChecking=yes"
|
|
proxy_command+=" -o UserKnownHostsFile=${known_hosts_path}"
|
|
proxy_command+=" -W %h:%p ${release_proxy}"
|
|
ssh_options+=(-o "ProxyCommand=${proxy_command}")
|
|
fi
|
|
|
|
remote_args=("${service_group}" "${run_id}" "${image_refs[@]}")
|
|
if [[ "${refresh_data}" == true ]]; then
|
|
remote_args+=(--refresh-data)
|
|
fi
|
|
printf -v remote_command '%q ' "${release_service_path}" "${remote_args[@]}"
|
|
remote_command="${remote_command% }"
|
|
|
|
# The command is deliberately assembled and validated on the client.
|
|
# shellcheck disable=SC2029
|
|
ssh "${ssh_options[@]}" \
|
|
"${release_user}@${release_host}" \
|
|
"${remote_command}"
|