Terraform external数据源调用Bash脚本返回JSON结构错误排查
问题根因
报错的核心原因是你的Bash脚本向标准输出(stdout)输出了除最终JSON之外的额外内容,Terraform external数据源会将脚本所有stdout输出视为待解析的JSON,额外内容会直接破坏JSON结构。你遇到的invalid character 'R'错误,就来自脚本中openssl rsa -in public_key -pubin -text -noout命令的输出内容开头的R(该命令输出第一行为RSA Public-Key: (2048 bit))。
你之前猜测的密码特殊字符问题不存在,因为最后用jq生成JSON时,jq会自动对特殊字符做转义处理,只要保证stdout只有jq输出的JSON即可。
可行解决方案
按以下规则修改你的Bash脚本即可:
- 屏蔽所有中间命令的stdout输出,非最终JSON的提示信息全部输出到stderr(Terraform会将stderr内容作为日志展示,不会影响JSON解析)
- 增加实例密码生成的重试等待逻辑,避免实例刚创建时串行端口还没生成密码导致返回异常内容
- 所有变量引用加双引号,避免特殊字符拆分参数
修改后的完整Bash脚本
#!/bin/bash USERNAME="kerbuser1" USER_EMAIL="kerbuser1@f5lbu.com" EXPIRE_DATE=$(date --date='now +10 minutes' +%Y-%m-%dT%H:%M:%SZ) WORKING_DIR="/tmp/krbloc" #Variables provided in external block query format of the instance tf template eval "$(jq -r '@sh "HOSTNAME=\(.HOSTNAME) ZONE=\(.ZONE) ID=\(.ID)"')" # generate rsa public and private keys mkdir -p ${WORKING_DIR} cd ${WORKING_DIR} openssl genrsa -out private_key 2048 > /dev/null 2>&1 openssl rsa -pubout -in private_key -out public_key > /dev/null 2>&1 # 屏蔽该行的stdout输出,避免污染JSON openssl rsa -in public_key -pubin -text -noout > /dev/null 2>&1 MODULUS=$(cat public_key | grep -v -- ----- | base64 -d | dd bs=1 skip=33 count=256 2>/dev/null | base64 -w 0; echo) EXPONENT=$(cat public_key | grep -v -- ----- | base64 -d | dd bs=1 skip=291 count=3 2>/dev/null | base64) printf '{"userName": "%s", "modulus": "%s", "exponent": "%s", "expireOn": "%s", "email": "%s"}' "${USERNAME}" "${MODULUS}" "${EXPONENT}" "${EXPIRE_DATE}" "${USER_EMAIL}" > metadata_payload.json if [[ -f metadata_payload.json ]]; then # 屏蔽gcloud命令的正常输出 gcloud compute instances add-metadata ${HOSTNAME} --project ${ID} --zone ${ZONE} --metadata-from-file="windows-keys=metadata_payload.json" > /dev/null 2>&1 else # 错误信息输出到stderr >&2 echo "Payload file not found." exit 1 fi # 增加重试逻辑,最多等300秒,等待密码生成 MAX_RETRY=30 RETRY_COUNT=0 KRB="" while [[ -z "$KRB" && $RETRY_COUNT -lt $MAX_RETRY ]]; do KRB=$(gcloud compute instances get-serial-port-output ${HOSTNAME} --project=${ID} --zone=${ZONE} --port=4 2>/dev/null | jq -r -c --arg MODULUS "$MODULUS" 'select(.modulus == $MODULUS) | .encryptedPassword') if [[ -z "$KRB" ]]; then RETRY_COUNT=$((RETRY_COUNT+1)) sleep 10 fi done if [[ -z "$KRB" ]]; then >&2 echo "Failed to get encrypted password after 300 seconds" exit 1 fi # 变量加引号,避免特殊字符问题 PAS=$(echo "$KRB" | base64 -d | openssl rsautl -decrypt -inkey private_key -oaep 2>/dev/null) jq -n --arg passwd "$PAS" '{"password":$passwd}'
验证方法
修改完成后手动执行脚本,确认stdout仅输出最终的{"password":"xxx"}格式JSON,无其他任何内容即可。
内容的提问来源于stack exchange,提问作者Rukmangathan
相关产品推荐
相关产品推荐

