admin管理员组

文章数量:1312888

In my multi-stage YAML pipeline, we use deployment jobs that uses an Azure Pipeline Environment that has a Invoke Azure Function check in the Approvals and Checks. We're using the asynchronous model, where the function app is responsible for issuing the approval based on our business criteria. The function app callback works well but we're having issues with providing stage-specific configuration.

In the "Invoke Azure Function" check, we provide a JSON payload that contains variables defined in macro syntax. I'm seeing the following behaviour with our payload:

  • pre-defined variables work as expected
  • global pipeline variables are resolved correctly
  • variables that are scoped to the stage do not get resolved
# POST BODY of Invoke Azure Function:
{
  # these values are fine because they're all predefined variables
  "projectId": "$(system.TeamProjectId)", 
  "hubName": "$(system.HostType)", 
  "planId": "$(system.PlanId)", 
  "buildId": "$(build.BuildId)",
  "jobId": "$(system.JobId)", 
  "timelineId": "$(system.TimelineId)", 
  "taskInstanceId": "$(system.TaskInstanceId)", 
  "taskInstanceName": "$(system.TaskInstanceName)",

  # this variable works because it's defined at the root level of the pipeline
  "deploymentType": "$(PipelineDescription)",

  # these variables do not work because they're defined in the stage
  "customer": "$(Customer)",
  "environment": "$(Environment)"
}

Although I don't believe the structure of the pipeline is relevant, for visibility sake, post-compilation our pipeline looks something like this:

trigger: none

variables:
  pipelineDescription: "Maintenance Release"

stages:
- stage: build
  ...

- stage: deploy_qa
  dependsOn: [ build ]
  variables:
    Customer: "Internal"
    Environment: "QA Lab"
  jobs:
  - deployment: deploy_qa
    environment: qa
    strategy:
      runOnce:
        deploy:
          steps:
          - ....

- stage: deploy_customer1_test
  dependsOn: [ build ]
  variables:
    Customer: "Customer 1"
    Environment: "Test"
  jobs:
  - deployment: deploy_customer1_test
    environment: customer1_test
    strategy:
      runOnce:
        deploy:
          steps:
          - ....    

- stage: ...

When the pipeline runs, the payload that is sent to the function app looks like:

Request body: {
  "projectId": "<guid>",
  "hubName": "checks",
  "planId": "553d1a03-abcf-55d9-8a1b-270cb656a2fe",
  "buildId": "2066",
  "jobId": "5f551683-56b4-55d5-de67-f7114e5b3c79",
  "timelineId": "553d1a03-abcf-55d9-8a1b-270cb656a2fe",
  "taskInstanceId": "6b8648fe-fcd2-5c33-fe67-eba2c9dd8146",
  "taskInstanceName": "AzureFunction0",
  "deploymentType": "Maintenance Release",
  "customer":    "$(Customer)",
  "environment": "$(Environment)"
}

Although our pipeline has the values defined, it appears as though the check does not evaluate or include the variables from the stage.

I have discovered that it is possible to link the check to a variable group, which works. However, as we have hundreds of environments and existing YAML variable definitions for each, we'd prefer to keep things in source, avoid duplication and in general avoid using variable groups wherever possible.

Any thoughts on how to resolve these values? For example a REST API that I can use to evaluate the missing variables?

In my multi-stage YAML pipeline, we use deployment jobs that uses an Azure Pipeline Environment that has a Invoke Azure Function check in the Approvals and Checks. We're using the asynchronous model, where the function app is responsible for issuing the approval based on our business criteria. The function app callback works well but we're having issues with providing stage-specific configuration.

In the "Invoke Azure Function" check, we provide a JSON payload that contains variables defined in macro syntax. I'm seeing the following behaviour with our payload:

  • pre-defined variables work as expected
  • global pipeline variables are resolved correctly
  • variables that are scoped to the stage do not get resolved
# POST BODY of Invoke Azure Function:
{
  # these values are fine because they're all predefined variables
  "projectId": "$(system.TeamProjectId)", 
  "hubName": "$(system.HostType)", 
  "planId": "$(system.PlanId)", 
  "buildId": "$(build.BuildId)",
  "jobId": "$(system.JobId)", 
  "timelineId": "$(system.TimelineId)", 
  "taskInstanceId": "$(system.TaskInstanceId)", 
  "taskInstanceName": "$(system.TaskInstanceName)",

  # this variable works because it's defined at the root level of the pipeline
  "deploymentType": "$(PipelineDescription)",

  # these variables do not work because they're defined in the stage
  "customer": "$(Customer)",
  "environment": "$(Environment)"
}

Although I don't believe the structure of the pipeline is relevant, for visibility sake, post-compilation our pipeline looks something like this:

trigger: none

variables:
  pipelineDescription: "Maintenance Release"

stages:
- stage: build
  ...

- stage: deploy_qa
  dependsOn: [ build ]
  variables:
    Customer: "Internal"
    Environment: "QA Lab"
  jobs:
  - deployment: deploy_qa
    environment: qa
    strategy:
      runOnce:
        deploy:
          steps:
          - ....

- stage: deploy_customer1_test
  dependsOn: [ build ]
  variables:
    Customer: "Customer 1"
    Environment: "Test"
  jobs:
  - deployment: deploy_customer1_test
    environment: customer1_test
    strategy:
      runOnce:
        deploy:
          steps:
          - ....    

- stage: ...

When the pipeline runs, the payload that is sent to the function app looks like:

Request body: {
  "projectId": "<guid>",
  "hubName": "checks",
  "planId": "553d1a03-abcf-55d9-8a1b-270cb656a2fe",
  "buildId": "2066",
  "jobId": "5f551683-56b4-55d5-de67-f7114e5b3c79",
  "timelineId": "553d1a03-abcf-55d9-8a1b-270cb656a2fe",
  "taskInstanceId": "6b8648fe-fcd2-5c33-fe67-eba2c9dd8146",
  "taskInstanceName": "AzureFunction0",
  "deploymentType": "Maintenance Release",
  "customer":    "$(Customer)",
  "environment": "$(Environment)"
}

Although our pipeline has the values defined, it appears as though the check does not evaluate or include the variables from the stage.

I have discovered that it is possible to link the check to a variable group, which works. However, as we have hundreds of environments and existing YAML variable definitions for each, we'd prefer to keep things in source, avoid duplication and in general avoid using variable groups wherever possible.

Any thoughts on how to resolve these values? For example a REST API that I can use to evaluate the missing variables?

Share Improve this question edited Feb 1 at 7:31 Daniel Mann 59.1k13 gold badges105 silver badges127 bronze badges Recognized by CI/CD Collective asked Jan 31 at 21:20 bryanbcookbryanbcook 18.4k2 gold badges47 silver badges79 bronze badges 11
  • 1 Can you use a serverless job and call the AzureFunctions@1 task instead of using it as an environment-defined gate? – Daniel Mann Commented Feb 1 at 7:21
  • Hey @braynbcook , can you share the pre-compilation syntax for the variables defined within the stage, for Customer and Environment? Are they defined using macro syntax $(Customer) for example, or are they hardcoded values as shown in your post-compilation snippet? I'll experiment to find a solution and get back with an answer if I find one over the next couple days. Interesting question! – Scott Richards Commented Feb 1 at 7:28
  • 1 Another alternative: check the "Call into Azure DevOps" section of the docs -- your function app can call back into Azure DevOps via the job PAT. – Daniel Mann Commented Feb 1 at 7:29
  • If possible, please show the step where you provide the JSON payload, I can only speculate until that. – GalnaGreta Commented Feb 1 at 9:02
  • @ScottRichards the supplied pipeline produces the same result. They’re hard coded in the pipeline using macro syntax – bryanbcook Commented Feb 1 at 16:40
 |  Show 6 more comments

1 Answer 1

Reset to default 0

I have tried several variations to find a solution for this, and have found one that might work for you. I agree that it does seem like the stage variables should be in scope at the time the function is being triggered, but I could not find any way to access them. Here is a workaround.


Proposed solution

Keeping in mind your statement here we'd prefer to keep things in source, avoid duplication and in general avoid using variable groups wherever possible., the solution I found was that you are able to access the stage name via the predefined variable $(System.StageName). So, if you were to rename your stage names to include the two variables you are trying to reference, your function could programmatically split these for use.

POST BODY of Invoke Azure Function:

{
  # these values are fine because they're all predefined variables
  "projectId": "$(system.TeamProjectId)", 
  "hubName": "$(system.HostType)", 
  "planId": "$(system.PlanId)", 
  "buildId": "$(build.BuildId)",
  "jobId": "$(system.JobId)", 
  "timelineId": "$(system.TimelineId)", 
  "taskInstanceId": "$(system.TaskInstanceId)", 
  "taskInstanceName": "$(system.TaskInstanceName)",

  # this variable works because it's defined at the root level of the pipeline
  "deploymentType": "$(PipelineDescription)",

  # these are unobtainable
  "customer": "$(Customer)",
  "environment": "$(Environment)",

  # NEW PROPOSED VARIABLE
  "stageName": "$(system.StageName)"
}

Example .yaml snippet (edited from your question):

- stage: deploy_qa-internal-qalab # stageName is accessible in invoke function
  dependsOn: [ build ]
  variables:
    Customer: "Internal"  # variable not accessible in invoke function
    Environment: "QA Lab" # variable not accessible in invoke function
  jobs:
  - deployment: deploy_qa
    environment: qa
    strategy:
      runOnce:
        deploy:
          steps:
          - ....

Example request body (edited from your question):

Request body: {
  "projectId": "<guid>",
  "hubName": "checks",
  "planId": "553d1a03-abcf-55d9-8a1b-270cb656a2fe",
  "buildId": "2066",
  "jobId": "5f551683-56b4-55d5-de67-f7114e5b3c79",
  "timelineId": "553d1a03-abcf-55d9-8a1b-270cb656a2fe",
  "taskInstanceId": "6b8648fe-fcd2-5c33-fe67-eba2c9dd8146",
  "taskInstanceName": "AzureFunction0",
  "deploymentType": "Maintenance Release",
  "stageName":   "deploy_qa-internal-qalab", # this is new
  "customer":    "$(Customer)",
  "environment": "$(Environment)"
}

Note that stageName is now accessiable - your function could now split on -, and store the those into variables as required.


To list down other options I tried for reference:

  • using different syntaxes to reference variables (macro, template expression, and runtime expression) within the Invoke Azure Function headers, as defined here: https://learn.microsoft/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#understand-variable-syntax
    • the only syntax that worked to expand a variable was macro, $()
  • moving the stage into a template, parsing a parameter to the template for the stage variables, and attempting to reference the template parameter in the Invoke Azure Function Headers.
  • this stack overflow question is similar, but no relevant answer: Access build pipeline output variable in Environment Approval / Check
  • this documentation could be helpful, but didn't really clarify why it isn't working in a clear manner (from my understanding): https://learn.microsoft/en-us/azure/devops/pipelines/process/runs?view=azure-devops#pipeline-processing

本文标签: azure devopsEvaluate YAML Stage Variables in Environment Function App GateStack Overflow