You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 09:36:03