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

GitHub Action跨Job传递带引号字符串时引号丢失问题排查

问题:GitHub Action传递带引号的list(string)时引号丢失,导致Terraform无法识别

问题详情

用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 01:44:53