GitHub Action跨Job传递带引号字符串时引号丢失问题排查
问题详情
用Python脚本生成带引号的list(string)格式字符串(如["10.0.0.1/24"])作为GitHub Action输出,传递到下一个执行Terraform Plan的Job时,引号被自动移除,变成[10.0.0.1/24],导致Terraform无法识别该list(string)类型变量。但直接静态输入["10.0.0.1/24"]时,Terraform能正常执行。
相关Workflow代码:
createVNET: uses: redacted/gitops-tf-azure-vnet/.github/workflows/plan.yml@main with: ref: "main" AZURE_TENANT_ID: "redacted" AZURE_CLIENT_ID: "redacted" AZURE_SUBSCRIPTION_ID: "redacted" state_resource_group: "redacted" state_storageaccount: "redacted" state_container: "redacted" tgtresourceGroup: "redacted" tgtvnetName: "redacted" tgtLocation: "redacted" tgtAddress: ${{needs.output_stuff.outputs.vnet}} tgtSubnet: ${{needs.output_stuff.outputs.subnet}} subnetName: ${{needs.output_stuff.outputs.name}} secrets: inherit needs: output_stuff reserveSubnet: # 生成网络信息的Python脚本所在Job uses: redacted/ipFinder/.github/workflows/apply.yml@main with: ref: "main" az_sub : "TEST" # 可选TEST或PROD app_size : "S" app_deployment: "AZ" app_name: "TestAPP1" state_container: "redacted" vnet : "redacted" secrets: inherit output_stuff: runs-on: labels: ephemeral outputs: subnet: ${{ steps.subnets.outputs.subnet }} name: ${{ steps.names.outputs.name }} vnet: ${{ steps.vnets.outputs.vnet }} steps: - name: Output Chosen Subnet id: subnets run: | echo subnet=${{needs.reserveSubnet.outputs.cidr}} >> $GITHUB_OUTPUT - name: Output Chosen Appname id: names run: | echo name=${{needs.reserveSubnet.outputs.appName}} >> $GITHUB_OUTPUT - name: Output Chosen vnet id: vnets run: | echo vnet=${{needs.reserveSubnet.outputs.vnet}} >> $GITHUB_OUTPUT needs: reserveSubnet
Python输出相关代码:
def createNetwork(next_available_net,appName,username,password,vnet):# 在参考容器内创建网络并添加应用名称注释 connection = default_redacted_connection(username,password) for x in next_available_net['networks']: network = objects.Network.create(connection, cidr=x, comment=appName) cidr = x output_file = os.getenv('GITHUB_OUTPUT') cidr=f'["{cidr}"]' vnet=f'["{vnet}"]'
写入输出的代码:
with open(output_file, "a") as github_output: github_output.write(f"cidr={cidr}\ ") github_output.write(f"name={appName}\ ") github_output.write(f"vnet={vnet}\ ")
原因确认
这是GitHub Actions的设计行为:当通过GITHUB_OUTPUT传递字符串时,GitHub会自动解析输出内容,剥离外层的引号,因为它默认将输出视为纯文本而非结构化的JSON。即使在输出中手动添加引号,跨Job传递时也会被移除。
解决方案
方案1:输出标准JSON,用fromJSON解析传递
修改Python脚本,直接输出原始列表结构,并用json.dumps转成标准JSON字符串,避免手动拼接引号;然后在接收Job中用GitHub Actions的fromJSON函数解析,确保类型正确传递。
修改后的Python代码:
import json def createNetwork(next_available_net,appName,username,password,vnet): connection = default_redacted_connection(username,password) for x in next_available_net['networks']: network = objects.Network.create(connection, cidr=x, comment=appName) cidr = x output_file = os.getenv('GITHUB_OUTPUT') # 直接生成列表,转成标准JSON cidr_json = json.dumps([cidr]) vnet_json = json.dumps([vnet]) with open(output_file, "a") as github_output: github_output.write(f"cidr={cidr_json}\ ") github_output.write(f"name={appName}\ ") github_output.write(f"vnet={vnet_json}\ ")
然后在createVNET Job的参数中用fromJSON解析:
tgtAddress: ${{ fromJSON(needs.output_stuff.outputs.vnet) }} tgtSubnet: ${{ fromJSON(needs.output_stuff.outputs.subnet) }}
方案2:在输出步骤中转义引号
如果不想修改Python脚本,可以在output_stuff的步骤中,对输出值的引号进行转义,确保传递时引号保留:
- name: Output Chosen Subnet id: subnets run: | # 用sed转义引号,将"替换为\" escaped_cidr=$(echo '${{needs.reserveSubnet.outputs.cidr}}' | sed 's/"/\\"/g') echo subnet=$escaped_cidr >> $GITHUB_OUTPUT - name: Output Chosen vnet id: vnets run: | escaped_vnet=$(echo '${{needs.reserveSubnet.outputs.vnet}}' | sed 's/"/\\"/g') echo vnet=$escaped_vnet >> $GITHUB_OUTPUT
方案3:在Terraform中处理输入
如果前面的方法都无法实施,可以在Terraform代码中添加逻辑,将不带引号的输入转换为list(string)类型:
variable "tgtAddress" { type = list(string) default = [] } variable "tgtSubnet" { type = list(string) default = [] } locals { # 将不带引号的[10.0.0.1/24]转换为["10.0.0.1/24"]后解码 parsed_tgtAddress = contains(join("", var.tgtAddress), "[") ? jsondecode(replace(join("", var.tgtAddress), "/([a-zA-Z0-9./]+)/", "\"$1\"")) : var.tgtAddress parsed_tgtSubnet = contains(join("", var.tgtSubnet), "[") ? jsondecode(replace(join("", var.tgtSubnet), "/([a-zA-Z0-9./]+)/", "\"$1\"")) : var.tgtSubnet }
之后在Terraform代码中使用local.parsed_tgtAddress和local.parsed_tgtSubnet即可。
内容的提问来源于stack exchange,提问作者pcam

