5
votes

Normal (non-template) jobs in Azure DevOps yaml support inter-job variable passing as follows:

jobs:
- job: A
  steps:
  - script: "echo ##vso[task.setvariable variable=skipsubsequent;isOutput=true]false"
    name: printvar

- job: B
  condition: and(succeeded(), ne(dependencies.A.outputs['printvar.skipsubsequent'], 'true'))
  dependsOn: A
  steps:
  - script: echo hello from B

How do I do something similar in the following, given that templates don't support the dependsOn syntax? I need to get an output from the first template and pass it as 'environmentSlice' to the second template.

- stage: Deploy
  displayName: Deploy stage
  jobs:
  - template: build-templates/get-environment-slice.yml@templates
    parameters:
      configFileLocation: 'config/config.json'

  - template: build-templates/node-app-deploy.yml@templates
    parameters:
      # Build agent VM image name
      vmImageName: $(Common.BuildVmImage)
      environmentPrefix: 'Dev'
      environmentSlice: '-$(dependencies.GetEnvironmentSlice.outputs['getEnvironmentSlice.environmentSlice'])'

The reason I want the separation between the two templates is the second one is a deployment template and I would like input from the first template in naming the environment in the second template. I.e. initial part of node-app-deploy.yml (2nd template) is:

  jobs:
  - deployment: Deploy
    displayName: Deploy
    # Because we use the environmentSlice to name the environment, we have to have it passed in rather than 
    # extracting it from the config file in steps below
    environment: ${{ parameters.environmentPrefix }}${{ parameters.environmentSlice }}

Update:

The accepted solution does allow you to pass variables between separate templates, but won't work for my particular use case. I wanted to be able to name the 'environment' section of the 2nd template dynamically, i.e. environment: ${{ parameters.environmentPrefix }}${{ parameters.environmentSlice }}, but this can only be named statically since templates are compiled on pipeline startup.

The downside of the solution is that it introduces a hidden coupling between the templates. I would have preferred the calling pipeline to orchestrate the parameter passing between templates.

2
Link to Azure documentation to use outputs in a different job - Nick Graham

2 Answers

3
votes

You can apply the depend on and dependency variable into templates.

See below sample:

To make sample more clear, here has 2 template files, one is azure-pipelines-1.yml, and another is azure-pipeline-1-copy.yml.

In azure-pipelines-1.yml, specify the environment value as output variable:

parameters:
  environment: ''
jobs:
- job: preDeploy
  variables:
    EnvironmentName: preDeploy-${{ parameters.environment }}
  steps:
  - checkout: none
  - pwsh: |
      echo "##vso[task.setvariable variable=EnvironmentName;isOutput=true]$($env:ENVIRONMENTNAME)"
    name: outputVars

And then, in azure-pipeline-1-copy.yml use dependency to get this output variable:

jobs:
- job: deployment
  dependsOn: preDeploy
  variables:
    EnvironmentNameCopy: $[dependencies.preDeploy.outputs['outputVars.EnvironmentName']]
  steps:
  - checkout: none
  - pwsh: |
      Write-Host "$(EnvironmentNameCopy)"
    name: outputVars

At last, in YAML pipeline, just need to pass the environment value

stages:
  - stage: deployQA
    jobs:
    - template: azure-pipelines-1.yml
      parameters:
        environment: FromTemplate1
    - template: azure-pipeline-1-copy.yml

Now, you can see the value get successfully in the second template job:

enter image description here

1
votes

It is possible to avoid the dependency in the called template. However, as the OP says, the environment name cannot be created dynamically.

Here is an example of the "calling" template, which firstly calls another template (devops-variables.yml) that sets some environment variables that we wish to consume in a later template (devops-callee.yml):

stages:
- stage: 'Caller_Stage'
  displayName: 'Caller Stage'

  jobs:
  - template: 'devops-variables.yml'
    parameters:
      InitialEnvironment: "Development"

  - template: 'devops-callee.yml'
    parameters:
      SomeParameter: $[dependencies.Variables_Job.outputs['Variables_Job.Variables.SomeParameter']]

In the devops-variables.yml file, I have this:

"##vso[task.setvariable variable=SomeParameter;isOutput=true;]Wibble"

Then, in the "devops-callee.yml", I just consume it something like this:

  parameters:
  - name: SomeParameter
    default: ''

  jobs:
  - deployment: 'Called_Job'
    condition: succeeded()
    displayName: 'Called Job'
    environment: "Development"
    pool:
      vmImage: 'windows-2019'
    dependsOn:
    - Variables_Job
    variables:
      SomeParameter: ${{parameters.SomeParameter}}
    strategy:
      runOnce:
        deploy:
          steps:
          - download: none
          - task: AzureCLI@2
            condition: succeeded()
            displayName: 'An Echo Task'
            inputs:
              azureSubscription: "$(TheServiceConnection)"
              scriptType: pscore
              scriptLocation: inlineScript
              inlineScript: |
                echo "Before"
                echo "$(SomeParameter)"
                echo "After"

Output:

2021-04-10T09:22:29.6188535Z Before
2021-04-10T09:22:29.6196620Z Wibble
2021-04-10T09:22:29.6197124Z After

This way, the callee doesn't reference the caller. Unfortunately, setting the environment in the callee thus:

environment: "$(SomeParameter)"

doesn't work - you'll just get an environment with the literal characters '$(SomeParameter)'.