feat: load-sops: +generic

This commit is contained in:
2026-06-09 14:51:11 +00:00
parent fcc72192f5
commit b9eabca464
2 changed files with 205 additions and 0 deletions
+204
View File
@@ -0,0 +1,204 @@
load_sops_shell_quote() {
printf "'"
printf '%s' "$1" | sed "s/'/'\"'\"'/g"
printf "'"
}
load_sops_normalize_key() {
load_sops_normalized_key=$(printf '%s' "$1" | tr '.-' '__' | tr '[:lower:]' '[:upper:]')
case "$load_sops_normalized_key" in
''|[!A-Z_]*|*[!A-Z0-9_]*)
printf 'load-sops: invalid environment name after key normalization\n' >&2
return 1
;;
esac
printf '%s\n' "$load_sops_normalized_key"
}
load_sops_env_from_sops_file() {
load_sops_file=$1
load_sops_extract=${2-}
if ! command -v sops >/dev/null 2>&1; then
printf 'load-sops: required tool `sops` not found\n' >&2
return 1
fi
if ! command -v yq >/dev/null 2>&1; then
printf 'load-sops: required tool `yq` not found\n' >&2
return 1
fi
load_sops_decrypted=''
load_sops_attempt=0
load_sops_max_retries=${LOAD_SOPS_MAX_RETRIES:-1}
load_sops_prompt=${LOAD_SOPS_PROMPT:-0}
while :; do
if [ -n "$load_sops_extract" ]; then
if load_sops_decrypted=$(sops -d --extract "$load_sops_extract" "$load_sops_file" 2>/dev/null); then
load_sops_status=0
else
load_sops_status=$?
fi
else
if load_sops_decrypted=$(sops -d "$load_sops_file" 2>/dev/null); then
load_sops_status=0
else
load_sops_status=$?
fi
fi
if [ "$load_sops_status" -eq 0 ]; then
break
fi
if [ "$load_sops_prompt" != 1 ]; then
printf 'load-sops: failed to decrypt file\n' >&2
return 1
fi
if ! [ -t 0 ] || ! [ -r /dev/tty ]; then
printf 'load-sops: decrypt failed and prompt requested, but no TTY is available\n' >&2
return 1
fi
load_sops_attempt=$((load_sops_attempt + 1))
if [ "$load_sops_max_retries" != 0 ] && [ "$load_sops_attempt" -gt "$load_sops_max_retries" ]; then
printf 'load-sops: decrypt failed after configured retries\n' >&2
return 1
fi
load_sops_use_script=${LOAD_SOPS_USE_SCRIPT:-auto}
load_sops_quoted_file=$(load_sops_shell_quote "$load_sops_file") || return 1
load_sops_quoted_tty=$(load_sops_shell_quote "$(tty)") || return 1
case "$load_sops_use_script" in
auto)
if command -v script >/dev/null 2>&1 && [ -t 0 ]; then
if script -qefc "env GPG_TTY=$load_sops_quoted_tty sops --output /dev/null -d $load_sops_quoted_file" /dev/null >/dev/null 2>&1; then
load_sops_script_status=0
else
load_sops_script_status=$?
fi
if [ "$load_sops_script_status" -eq 0 ]; then
continue
fi
fi
;;
1)
if ! command -v script >/dev/null 2>&1; then
printf 'load-sops: required tool `script` not found\n' >&2
return 1
fi
if script -qefc "env GPG_TTY=$load_sops_quoted_tty sops --output /dev/null -d $load_sops_quoted_file" /dev/null >/dev/null 2>&1; then
load_sops_script_status=0
else
load_sops_script_status=$?
fi
if [ "$load_sops_script_status" -eq 0 ]; then
continue
fi
;;
0)
;;
*)
printf 'load-sops: LOAD_SOPS_USE_SCRIPT must be auto, 0, or 1\n' >&2
return 1
;;
esac
printf 'load-sops: enter SOPS_AGE_KEY_CMD: ' >/dev/tty
if ! IFS= read -r SOPS_AGE_KEY_CMD </dev/tty; then
printf 'load-sops: failed to read prompt input\n' >&2
return 1
fi
export SOPS_AGE_KEY_CMD
done
load_sops_env_from_yaml_text "$load_sops_decrypted"
}
load_sops_env_from_yaml_file() {
load_sops_file=$1
if ! command -v yq >/dev/null 2>&1; then
printf 'load-sops: required tool `yq` not found\n' >&2
return 1
fi
load_sops_env_from_yaml_text "$(cat "$load_sops_file")"
}
load_sops_env_from_yaml_text() {
load_sops_yaml=$1
load_sops_seen=''
if load_sops_keys=$(printf '%s' "$load_sops_yaml" | yq -r 'keys | .[]' 2>/dev/null); then
load_sops_keys_status=0
else
load_sops_keys_status=$?
fi
if [ "$load_sops_keys_status" -ne 0 ]; then
printf 'load-sops: failed to inspect YAML top-level keys\n' >&2
return 1
fi
while IFS= read -r load_sops_key; do
[ -n "$load_sops_key" ] || continue
load_sops_name=$(load_sops_normalize_key "$load_sops_key") || return 1
case "
$load_sops_seen
" in
*"
$load_sops_name
"*)
if [ "${LOAD_SOPS_ALLOW_COLLISIONS:-0}" != 1 ]; then
printf 'load-sops: normalized environment name collision\n' >&2
return 1
fi
;;
esac
load_sops_seen=${load_sops_seen}${load_sops_seen:+"
"}$load_sops_name
load_sops_kind=$(printf '%s' "$load_sops_yaml" | yq -r '."'"$load_sops_key"'" | kind' 2>/dev/null) || {
printf 'load-sops: failed to inspect YAML value kind\n' >&2
return 1
}
load_sops_tag=$(printf '%s' "$load_sops_yaml" | yq -r '."'"$load_sops_key"'" | tag' 2>/dev/null) || {
printf 'load-sops: failed to inspect YAML value tag\n' >&2
return 1
}
if [ "$load_sops_kind" != scalar ]; then
printf 'load-sops: top-level YAML values must be scalars\n' >&2
return 1
fi
if [ "$load_sops_tag" = '!!null' ]; then
printf 'load-sops: top-level YAML null values are not supported\n' >&2
return 1
fi
if [ "${LOAD_SOPS_OVERWRITE:-1}" = 0 ]; then
eval 'load_sops_already_set=${'"$load_sops_name"'+x}'
if [ -n "$load_sops_already_set" ]; then
continue
fi
fi
load_sops_value=$(printf '%s' "$load_sops_yaml" | yq -r '."'"$load_sops_key"'"' 2>/dev/null) || {
printf 'load-sops: failed to read YAML scalar value\n' >&2
return 1
}
load_sops_quoted=$(load_sops_shell_quote "$load_sops_value") || return 1
eval "export $load_sops_name=$load_sops_quoted"
done <<EOF
$load_sops_keys
EOF
}