Microsoft Dev Ops yaml variables between deployments - azure

So at it's core what I'm essentially trying to do is this:
https://learn.microsoft.com/en-us/azure/devops/pipelines/process/deployment-jobs?view=azure-devops&preserve-view=true#support-for-output-variables
Which breaks down to the fact that I have a single deployment template that produces an output variable that I want to map back to a variable in my main template so that I can use it in subsequent deployment templates.
I have created a github project with all of my yaml templates here
https://github.com/MCKRUZ/azure-pipelines
but the crux of the problem lies in these few lines. First I'm calling a template deployment in my main "development.yaml" file.
# Deploy
# ------
- stage: Deploy
displayName: Deploy - Development
jobs:
- template: jobs/jobs-deploy-spa-arm-template.yaml
parameters:
environment: '$(environment)'
serviceConnection: '$(serviceConnection)'
subscriptionId: '$(subscriptionId)'
resourceGroup: '$(resourceGroup)'
storageAccountName: '$(storageAccountName)'
cdnEndpointName: '$(cdnEndpointName)'
cdnProfileName: '$(cdnProfileName)'
deploymentLocation: '$(deploymentLocation)'
armTemplateArtifactName: ${{ variables['armTemplateArtifactName'] }}
storageAccountCSMFilePath: '$(storageAccountCSMFilePath)'
storageAccountCSMParametersFile: '$(storageAccountCSMParametersFile)'
deployCDN: '${{ variables.isProduction }}'
cdnCSMFilePath: '$(cdnCSMFilePath)'
cdnCSMParametersFile: '$(cdnCSMParametersFile)'
That template looks like this:
# Parameters
# ---------
parameters:
- name: environment
type: string
- name: serviceConnection
type: string
- name: subscriptionId
type: string
- name: resourceGroup
type: string
- name: storageAccountName
type: string
- name: cdnEndpointName
type: string
- name: cdnProfileName
type: string
- name: deploymentLocation
type: string
- name: armTemplateArtifactName
type: string
- name: storageAccountCSMFilePath
type: string
- name: storageAccountCSMParametersFile
type: string
- name: deployCDN
type: boolean
default: false
- name: cdnCSMFilePath
type: string
- name: cdnCSMParametersFile
type: string
- name: overrideParameterFile
type: boolean
default: true
# Jobs
# ---------
jobs:
# Job - Deploy SPA ARM Template
# ---------
- #deployment: '${{ parameters.armTemplateArtifactName }}'
deployment: armTemplates
variables:
storageAccountNameCalculated: ''
cdnEndpointNameCalculated: ''
displayName: Deploy SPA ARM Template
environment: ${{ parameters.environment }}
workspace:
clean: all
strategy:
runOnce:
deploy:
steps:
# Bash - Show Input Parameter Variables
# ---------
- bash: |
echo ''
echo '===== Deploy SPA ARM Template Input Variables ====='
echo ''
echo ' ===== Parameters ====='
echo ' environment: ${{ parameters.environment }}'
echo ' serviceConnection: ${{ parameters.serviceConnection }}'
echo ' subscriptionId: ${{ parameters.subscriptionId }}'
echo ' resourceGroup: ${{ parameters.resourceGroup }}'
echo ' storageAccountName: ${{ parameters.storageAccountName }}'
echo ' cdnEndpointName: ${{ parameters.cdnEndpointName }}'
echo ' cdnProfileName: ${{ parameters.cdnProfileName }}'
echo ' deploymentLocation: ${{ parameters.deploymentLocation }}'
echo ' armTemplateArtifactName: ${{ parameters.armTemplateArtifactName }}'
echo ' storageAccountCSMFilePath: ${{ parameters.storageAccountCSMFilePath }}'
echo ' storageAccountCSMParametersFile: ${{ parameters.storageAccountCSMParametersFile }}'
echo ' deployCDN: ${{ parameters.deployCDN }}'
echo ' cdnCSMFilePath: ${{ parameters.cdnCSMFilePath }}'
echo ' cdnCSMParametersFile: ${{ parameters.cdnCSMParametersFile }}'
echo ''
displayName: Debug - Input Parameters
# Task - Deploy Storage Account ARM Template
# ---------
- task: AzureResourceManagerTemplateDeployment#3
displayName: Deploy Storage Account ARM Template
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: '${{ parameters.serviceConnection }}'
subscriptionId: '${{ parameters.subscriptionId }}'
action: 'Create Or Update Resource Group'
resourceGroupName: '${{ parameters.resourceGroup }}'
location: '${{ parameters.deploymentLocation }}'
templateLocation: 'Linked artifact'
csmFile: '$(Pipeline.Workspace)\${{ parameters.armTemplateArtifactName }}\${{ parameters.storageAccountCSMFilePath }}'
csmParametersFile: '$(Pipeline.Workspace)\${{ parameters.armTemplateArtifactName }}\${{ parameters.storageAccountCSMParametersFile }}'
deploymentMode: 'Incremental'
deploymentName: 'storageAccountDeployment'
deploymentOutputs: 'storageAccountDeploymentOutput'
${{ if eq(parameters.overrideParameterFile, 'true') }}:
overrideParameters: '-formatNames true -environment ${{ parameters.environment }} -storageAccountName ${{ parameters.storageAccountName }}'
# Task - Retrieve Output Variable
#----------
- task: AzurePowerShell#5
displayName: Retrieve Output Variables
name: retrieveStorageAccountDeploymentOutput
condition: succeeded()
inputs:
azureSubscription: '${{ parameters.serviceConnection }}'
scriptType: 'inlineScript'
azurePowerShellVersion: 'LatestVersion'
inline: |
$armOutput = '$(storageAccountDeploymentOutput)' | ConvertFrom-Json
Write-Output "##vso[task.setvariable variable=storageAccountNameCalculated;]$($armOutput.storageAccountName.value)"
- script: echo "##vso[task.setvariable variable=myOutputVar;isOutput=true]this is the deployment variable value"
name: setvarStep
- script: echo $(setvarStep.myOutputVar)
name: echovar
# Task - Add Static Hosting to Storage Account
# ---------
- task: AzurePowerShell#5
displayName: Add Static Hosting to Storage Account
condition: succeeded()
inputs:
azureSubscription: '${{ parameters.serviceConnection }}'
scriptType: 'inlineScript'
azurePowerShellVersion: latestVersion
inline: |
$StorageAccount = Get-AzStorageAccount -Name $(storageAccountNameCalculated) -ResourceGroupName '${{ parameters.resourceGroup }}'
Enable-AzStorageStaticWebsite -Context $StorageAccount.Context -IndexDocument index.html -ErrorDocument404Path index.html
# Task - Deploy CDN ARM Template
# ---------
- task: AzureResourceManagerTemplateDeployment#3
displayName: Deploy CDN ARM Template
condition: and(succeeded(), eq(${{ parameters.deployCDN }}, 'true'))
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: '${{ parameters.serviceConnection }}'
subscriptionId: '${{ parameters.subscriptionId }}'
action: 'Create Or Update Resource Group'
resourceGroupName: '${{ parameters.resourceGroup }}'
location: '${{ parameters.deploymentLocation }}'
templateLocation: 'Linked artifact'
csmFile: '$(Pipeline.Workspace)\${{ parameters.armTemplateArtifactName }}\${{ parameters.cdnCSMFilePath }}'
csmParametersFile: '$(Pipeline.Workspace)\${{ parameters.armTemplateArtifactName }}\${{ parameters.cdnCSMParametersFile }}'
deploymentMode: 'Incremental'
deploymentName: 'cdnDeployment'
deploymentOutputs: 'CDNDeployOutput'
${{ if eq(parameters.overrideParameterFile, 'true') }}:
overrideParameters: '-formatNames true -environment ${{ parameters.environment }} -cdnEndpointName ${{ parameters.cdnEndpointName }} -cdnProfileName ${{ parameters.cdnProfileName }} -storageAccountPrimaryEndpoint -storageAccountPrimaryEndpoint $storageAccountNameCalculated.z13.web.core.windows.net'
# Task - Retrieve Output Variable
#----------
- task: AzurePowerShell#5
displayName: Retrieve Output Variables
condition: and(succeeded(), eq(${{ parameters.deployCDN }}, 'true'))
inputs:
azureSubscription: '${{ parameters.serviceConnection }}'
scriptType: 'inlineScript'
azurePowerShellVersion: 'LatestVersion'
inline: |
$armOutput = '$(CDNDeployOutput)' | ConvertFrom-Json
Write-Output "##vso[task.setvariable variable=cdnEndpointNameCalculated;isOutput=true]$($armOutput.cdnEndpointName)"
# Bash - Show Output Parameter Variables
# ---------
- bash: |
echo ''
echo '===== Deploy SPA ARM Template Output Variables ====='
echo ''
echo ' ===== Parameters ====='
echo ' storageAccountNameCalculated: $(storageAccountNameCalculated)'
echo ' cdnEndpointName: $(cdnEndpointNameCalculated)'
echo ''
displayName: Debug - Output Parameters
Where I'm using these two lines
- script: echo "##vso[task.setvariable variable=myOutputVar;isOutput=true]this is the deployment variable value"
name: setvarStep
- script: echo $(setvarStep.myOutputVar)
name: echovar
To try and isolate a way that I can create a variable that will be able to be used back in my main yaml file. When I look at the logs, the second echo successfully echos out the variable so I know that part works at least. Once this runs, back in my main yaml file I call this.
- job: job_deploy_code
displayName: Deploy Some Code
dependsOn: armTemplates
variables:
myVarFromDeploymentJob: $[ dependencies.armTemplates.outputs['deploy_Development.setvarStep.myOutputVar'] ]
steps:
- script: "echo $(myVarFromDeploymentJob)"
name: echovar
Which is just supposed to echo back what is in my variable. Right now it's blank. I know it's some stupid syntax that I'm not seeing but I can't for the life of me figure out what it is.

I solved this with the correct syntax
- job: job_deploy_code
displayName: Deploy Some Code
dependsOn: armTemplates
variables:
myVarFromDeploymentJob: $[ dependencies.armTemplates.outputs['armTemplates.setvarStep.myOutputVar'] ]

Related

Is there a way to handle a single parameter as a build number OR source branch in an azure pipeline?

I'm trying to allow for a single variable that can specify the build id or branch to deploy from. This means I need to type coerce, and it's failing if I try to call a ge (greater than or equal) or lt (less than) on a string. It looks like I might have to use bash commands to do the type conversion for me, so I was wondering if anyone had this handy.
Here's the steps I'm using:
- ${{ each deployment in parameters.deployments }}:
- deployment: Deploy_${{deployment.serviceName}}_${{ parameters.region }}
pool:
vmImage: ${{ parameters.vmImage }}
displayName: 'Deploy ${{ deployment.serviceName }} ${{ parameters.region }}'
${{ if not(eq(parameters.kubernetesServiceEndpoint, '')) }}:
environment: ${{ parameters.kubernetesServiceEndpoint }}
${{ elseif not(and(eq(parameters.azureResourceGroup, ''), eq(parameters.kubernetesCluster, ''))) }}:
environment: ${{ parameters.environment }}
strategy:
runOnce:
deploy:
steps:
# Disable the automatic downloading of artifacts, because we will specifying exactly what we need
- download: none
- task: DownloadPipelineArtifact#2
condition: ${{ ge(0, deployment.branchBuildId) }}
inputs:
source: 'specific'
project: ${{ deployment.project }}
pipeline: ${{ deployment.pipeline }}
runVersion: 'latestFromBranch'
runBranch: ${{ deployment.branchBuildId }}
patterns: 'DeploymentData/*'
displayName: 'Download Latest Artifacts'
- task: DownloadPipelineArtifact#2
condition: ${{ lt(0, deployment.branchBuildId) }}
inputs:
source: 'specific'
project: ${{ deployment.project }}
pipeline: ${{ deployment.pipeline }}
runVersion: 'specific'
runId: ${{ deployment.branchBuildId }}
#runBranch: ${{ deployment.branch }}
patterns: 'DeploymentData/*'
displayName: 'Download Specific Artifacts (${{ deployment.buildId }})'
I want these to be mutually exclusive (run the first if pulling the latest from a specific branch, and run the second if downloading from a specific build id). By using a single parameter I can avoid another control parameter to decide which version should be used.
Two things:
1, If you use script to handle, then 'compile time usage' is unable to use.
You need to use runtime usage:
variables['xxx']
2, Variables only support passing string, you can compare them and then use logging command to output the result:
trigger:
- none
pool:
vmImage: ubuntu-latest
parameters:
- name: str1
default: 110
- name: str2
default: 130
steps:
- task: PowerShell#2
name: outputresult
inputs:
targetType: 'inline'
script: |
$str1 = "${{parameters.str1}}"
$str2 = "${{parameters.str2}}"
Write-Host $str1
Write-Host $str2
# powershell compare the strings greater than or less than
if ($str1 -gt $str2) {
$result = "greater than"
Write-Host $result
Write-Host "##vso[task.setvariable variable=result;isoutput=true]$result"
} elseif ($str1 -lt $str2) {
$result = "less than"
Write-Host $result
Write-Host "##vso[task.setvariable variable=result;isoutput=true]$result"
} else {
$result = "equal"
Write-Host $result
Write-Host "##vso[task.setvariable variable=result;isoutput=true]$result"
}
#Below is the handling logic
- task: PowerShell#2
condition: eq('greater than',variables['outputresult.result'])
inputs:
targetType: 'inline'
script: |
# Write your PowerShell commands here.
Write-Host "greater than"
- task: PowerShell#2
condition: eq('less than',variables['outputresult.result'])
inputs:
targetType: 'inline'
script: |
# Write your PowerShell commands here.
Write-Host "less than"
Result:

How do I reference a different Variable group per stage - stage per environment in environments - Azure Devops, template, variable groups

When I run the pipeline below I get an error within jobs.yml - it commplains about an "unexpected value" for the environment parameter... and within stages.yml "unexpected value: parameters" - what am I doing wrong here? The idea is to get the environments and the associated variableGroup - loop thru the environments array in Stages.yml and create a stage per environment... insert correct variableGroup per stage... use variablesGroup values to perform jobs in jobs.yml - each variable group contains the same vars.. with different values.
This is main.yml
#main.yml
trigger: none
extends:
template: /Build/Stages.yml
parameters:
environments:
- environment: Dev
variableGroup: Project-Dev-VarGrp
- environment: QA
variableGroup: Project-QA-VarGrp
dependsOn: Dev
- environment: UAT
variableGroup: Project-UAT-VarGrp
dependsOn: QA
- environment: UAT2
variableGroup: Project-UAT2-VarGrp
dependsOn: UAT
Then here is the next bit... Stages.yml
parameters:
- name: dataFolder
displayName: 'Data folder to process'
type: string
default: '/DataMigrations/Master_Data/'
- name: dataFiles
displayName: List of Data Files or Folder names
type: string
default: 'Dev_Data.zip'
- name: environments
type: object
default: []
stages:
- ${{ each environment in parameters.environments }}:
- stage: '${{ parameters.environment }}'
jobs:
- template: ./jobs.yml
variables:
- group: ${{ parameters.variableGroup }}
parameters:
environment: '${{ parameters.environment }}'
crmURL: $(crmURL)
oauthAppId: $(ClientID)
ClientPass: $(ClientPass)
dataFolder: '${{ parameters.dataFolder }}'
env: '${{ parameters.environment }}'
and here is jobs.yml
jobs:
- deployment: DeployData
displayName: 'Deploying Data to ${{ parameters.environment }}'
environment: ${{ parameters.environment }}
pool:
vmImage: 'windows-latest'
strategy:
runOnce:
deploy:
steps:
- checkout: self
clean: false
- powershell: |
Write-Host "##vso[task.setvariable variable=crmConnectionString]'AuthType=ClientSecret;Url=$(crmURL);ClientId=$(ClientID);ClientSecret=$(ClientPass)'"
displayName: 'PreDeploy configuration'
- task: PowerShell#2
displayName: 'Powershell: Run update-data.ps1'
inputs:
targetType: 'filePath'
filePath: $(System.DefaultWorkingDirectory)/DataMigrations/Scripts/update-data.ps1
arguments: -folderName '${{ parameters.dataFolder }}' -environment '${{ parameters.env }}'
workingDirectory: $(System.DefaultWorkingDirectory)/DataMigrations
- task: PowerShell#2
displayName: 'Powershell: Run zip-import-data.ps1'
inputs:
targetType: 'filePath'
filePath: $(System.DefaultWorkingDirectory)/DataMigrations/Scripts/zip-import-data.ps1
arguments: -folderName '${{ parameters.dataFolder }}' -dataMigrationFilenames '${{ parameters.dataFiles }}' -connectionString $(crmConnectionString)
workingDirectory: $(System.DefaultWorkingDirectory)/DataMigrations
Few things here:
When you're using ${{ each environment in parameters.environments }} then the nested environment and variableGroup are available using this syntax ${{ environment.environment }} and ${{ environment.variableGroup}}
In your Stages.yml file, you're trying to invoke the ./jobs.yml template but the associated parameters are defined after the - group: ${{ parameters.variableGroup }}. A valid syntax should looks like this:
stages:
- ${{ each environment in parameters.environments }}:
- stage: '${{ environment.environment }}'
variables:
- group: ${{ environment.variableGroup }}
jobs:
- template: ./jobs.yml
parameters:
environment: '${{ environment.environment }}'
crmURL: $(crmURL)
oauthAppId: $(ClientID)
ClientPass: $(ClientPass)
dataFolder: '${{ parameters.dataFolder }}'
env: '${{ environment.environment }}'
You also have few space typos. I know it's annoying but you need to have the exact yaml syntax otherwise the files can't be parsed.

Making an Azure Terraform pipeline work with multiple subscriptions

I am trying to get my main Terraform pipeline to deploy to multiple subscriptions, using the same service principle. Yet I keep getting errors that state resource group not found when it tries to deploy to the subscription. Both subscriptions are in the same tenant.
Here is my YAML Code:
parameters:
- name: terraformWorkingDirectory
type: string
default: '$(System.DefaultWorkingDirectory)/Terraform/'
- name: serviceConnection
type: string
default: 'JasonTestEnvManagmentGroup'
- name: azureSubscription
type: string
default: 'JasonTestEnvManagmentGroup'
- name: appconnectionname
type: string
default: 'JasonTestEnvManagmentGroup'
- name: RG
type: string
default: 'Jason_Testing_Terraform'
- name: azureLocation
type: string
default: 'UK South'
- name: terraformVersion
type: string
default: '1.0.4'
- name: artifactName
type: string
default: 'Website'
#- name: authartifactName
# type: string
#default: 'AuthServer'
# Only run against develop
#trigger:
# branches:
# include:
# - main
#pool:
#vmImage: "ubuntu-latest"
# Don't run against PRs
#pr: none
#stages:
#- stage: terraformStage
# displayName: Detect Drift
# jobs:
#- job: terraform_plan_and_apply
steps:
- checkout: self
- task: TerraformInstaller#0
displayName: "install"
inputs:
terraformVersion: ${{ parameters.terraformVersion }}
- task: TerraformTaskV2#2
displayName: "init"
inputs:
provider: "azurerm"
command: "init"
backendServiceArm: ${{ parameters.serviceConnection }}
backendAzureRmResourceGroupName: "TerraformBackendForCICTesting"
backendAzureRmStorageAccountName: "nsterraformstatestorage"
backendAzureRmContainerName: "devopsterraformstatefile"
backendAzureRmKey: "terraform.tfstate"
workingDirectory: ${{ parameters.terraformWorkingDirectory }}
- task: TerraformTaskV1#0
displayName: "plan"
inputs:
provider: "azurerm"
command: "plan"
commandOptions: "-input=false"
environmentServiceNameAzureRM: ${{ parameters.serviceConnection }}
workingDirectory: ${{ parameters.terraformWorkingDirectory }}
- task: TerraformTaskV1#0
displayName: "apply"
inputs:
provider: "azurerm"
command: "apply"
commandOptions: "-input=false -auto-approve"
environmentServiceNameAzureRM: ${{ parameters.serviceConnection }}
workingDirectory: ${{ parameters.terraformWorkingDirectory }}
#- stage: put_pipelines_files_in_place
# displayName: Putting Pipeline Files In Place
#- jobs:
#- job: apply_artifiact_to_web_app
# displayName: Putting Files In Place
# dependsOn: terraform_plan_and_apply
# Download Artifact File
#- download: none
- task: DownloadPipelineArtifact#2 # Website Artifact
displayName: 'Download Build Artifacts'
inputs:
artifact: ${{ parameters.artifactName }}
patterns: '/website/**/*.zip'
path: '$(Build.ArtifactStagingDirectory)/website/'
# deploy to Azure Web App
- task: AzureWebApp#1
displayName: 'Azure Web App Deploy: nsclassroom-dgyn27h2dfoyojc' #Website Deploy Artifact
inputs:
package: $(Build.ArtifactStagingDirectory)/website/**/*.zip
azureSubscription: ${{ parameters.azureSubscription }}
ConnectedServiceName: ${{ parameters.appconnectionname}}
appName: 'nsclassroom-dgyn27h2dfoyojc'
ResourceGroupName: ${{ parameters.RG}}
- task: DownloadPipelineArtifact#2 # Authentication Server Artifact
displayName: 'Download Build Artifacts'
inputs:
artifact: ${{ parameters.artifactName}}
patterns: '/authsrv/**/*.zip'
path: '$(Build.ArtifactStagingDirectory)/authsrv/'
# deploy to Azure Web App
- task: AzureWebApp#1
displayName: 'Azure Web App Deploy: nsclassroomauthentication-dgyn27h2dfoyojc' #Authentication Server Deploy Artifact
inputs:
package: $(Build.ArtifactStagingDirectory)/authsrv/**/*.zip
azureSubscription: ${{ parameters.azureSubscription }}
ConnectedServiceName: ${{ parameters.appconnectionname}}
appName: 'nsclassroomauthentication-dgyn27h2dfoyojc'
ResourceGroupName: ${{ parameters.RG}}
The agent is uses is the default Ubuntu agent.
The service connection I have tried two, one mapped to both of the subscriptions doesn't work. Another mapped and scoped to the Management group.
The original service principle did work until I brought in the second subscription.

Azure DevOps YAML template passing hashset/map/dictionary/object - key value?

Say I have a main azure devops pipeline azure-pipelines.yml in which I call into my template deploy.yml.
In the main pipeline I'd like to be able to declare either a variable or a parameter of type hashset / map / dictionary or any other key value structure that I can then pass to the template.
I can see that it's possible to pass in an object type, but I can't wrap my head it's usage. How could I achieve the following?
Note the appSettings: {"key1":"value1","key2":"value2"} in azure-pipelines.yml is a fantasy, but showcases pretty well how I'd like this to work.
azure-pipelines.yml:
trigger:
- main
- job: deploy
pool:
vmImage: ${{ parameters.poolVmImage }}
steps:
- template: deploy.yml
parameters:
azureServiceConnection: ${{ parameters.azureServiceConnection }}
resourceGroupName: 'foo'
appServiceName: 'bar'
appSettings: {"key1":"value1","key2":"value2"}
deploy.yml:
parameters:
- name: azureServiceConnection
- name: resourceGroupName
- name: appServiceName
- name: appSettings
steps:
- task: AzureCLI#2
displayName: Deploy zip
name: deployZip
inputs:
azureSubscription: ${{ parameters.azureServiceConnection }}
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az webapp deployment source config-zip \
-g ${{ parameters.resourceGroupName }} \
-n ${{ parameters.appServiceName }} \
--src ./deployment.zip
az webapp config appsettings set \
-g ${{ parameters.resourceGroupName }} \
-n ${{ parameters.appServiceName }} \
--settings ${{ parameters.appSettings }}
How could I achieve the following?
You can indeed use object type parameters.
But in the template, you cannot use multiple object parameters at once, so you need to use each expression to loop through each object.
For example: - ${{ each setting in parameters.appSettings }}:
Here is my sample, you could refer to:
Deploy.yml:
parameters:
- name: azureServiceConnection
- name: resourceGroupName
- name: appServiceName
- name: appSettings
type: object
default: []
steps:
- task: AzureCLI#2
displayName: Deploy zip
name: deployZip
inputs:
azureSubscription: ${{ parameters.azureServiceConnection }}
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az webapp deployment source config-zip \
-g ${{ parameters.resourceGroupName }} \
-n ${{ parameters.appServiceName }} \
--src ./deployment.zip
- ${{ each setting in parameters.appSettings }}:
- task: AzureCLI#2
displayName: Deploy settings
inputs:
azureSubscription: ${{ parameters.azureServiceConnection }}
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az webapp config appsettings set \
-g ${{ parameters.resourceGroupName }} \
-n ${{ parameters.appServiceName }} \
--settings ${{ setting }}
azure-pipelines.yml:
trigger:
- none
parameters:
- name: InstanceArgs
type: object
default: [key2=value2,key3=value3]
jobs:
- job: deploy
pool:
vmImage: windows-latest
steps:
- template: deploy.yml
parameters:
azureServiceConnection: '${{ parameters.azureServiceConnection }}'
resourceGroupName: 'foo'
appServiceName: 'bar'
appSettings: ${{ parameters.InstanceArgs }}
Note: Since the parameters in the template are obejct type , the same type of parameters need to be set in the main yaml file to pass the object.
Workflow: Pass the object type parameters to the template. The template will run the deploy zip command first. Then it will traverse each object passed, and use the config settings command to pass them to the webapp one by one.
Result:

Can you pass Variable Group values to sub template in yaml?

I'm trying to create a multi-stage pipeline which has Variable Groups defined for each stage of the pipeline. The goal is to pass values from the group as parameters to a sub template. It seems the value of the Group, defined at the stage, is not getting passed in to the sub template. It overrides the "DEFAULTVALUE" with an empty string.
pipeline.yml
trigger:
- none
pool:
name: 'Azure Pipelines'
vmImage: windows-latest
stages:
- stage: DEV
variables:
- group: my-group-dev
jobs:
- template: sub-template.yml
parameters:
env: 'dev'
subscriptionName: '$(SubscriptionName)' # This reference from the variable group doesn't get passed in
subscriptionId: '$(SubscriptionId)'
- stage: TEST
variables:
- group: my-group-test
jobs:
- template: sub-template.yml
parameters:
env: 'test'
subscriptionName: '$(SubscriptionName)' # This reference from the variable group doesn't get passed in
subscriptionId: '$(SubscriptionId)'
sub-template.yml
parameters:
env: 'DEFAULTVALUE'
subscriptionName: 'DEFAULTVALUE'
subscriptionId: 'DEFAULTVALUE'
jobs:
- deployment: ResourceDeployment
displayName: Deploy Resources ${{ parameters.env }}
environment: ${{ parameters.env }}
strategy:
runOnce:
deploy:
steps:
- task: AzureFileCopy#4
displayName: 'Upload ARM Templates'
inputs:
sourcePath: '$(Pipeline.Workspace)/templates'
azureSubscription: '${{ parameters.subscriptionName }}'
destination: 'azureBlob'
storage: 'my-storage-account'
containerName: 'arm'
name: AzureFileCopy
- task: AzureResourceManagerTemplateDeployment#3
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: '${{ parameters.subscriptionName }}'
subscriptionId: '${{ parameters.subscriptionId }}'
action: 'Create Or Update Resource Group'
resourceGroupName: 'my-resource-group'
location: 'eastus2'
templateLocation: 'URL of the file'
csmFileLink: '$(AzureFileCopy.StorageContainerUri)${{ parameters.env }}/templates/main.json$(AzureFileCopy.StorageContainerSasToken)'
I have also tried adding the variable group within the sub-template but that also doesn't parse correctly...
Does anyone know if this is possible?
It is a known issue that the service connection endpoint cannot be referenced in variable groups defined under stage level.
You can fix this issue by below workarounds:
1, Define the variable groups in the global level instead of stage level See below:
trigger:
- none
pool:
name: 'Azure Pipelines'
vmImage: windows-latest
# define the variable group under global level.
variables:
- group: my-group-dev
- group: my-group-test
stages:
- stage: DEV
jobs:
- template: sub-template.yml
parameters:
env: 'dev'
subscriptionName: '$(SubscriptionName)' # This reference from the variable group doesn't get passed in
subscriptionId: '$(SubscriptionId)'
- stage: TEST
jobs:
- template: sub-template.yml
parameters:
env: 'test'
subscriptionName: '$(SubscriptionName)' # This reference from the variable group doesn't get passed in
subscriptionId: '$(SubscriptionId)'
2, Link the variable groups on the UI page.
On the yaml pipeline edit page--> Click the 3dots-->Triggers-->Variables Tab-->Link Variable group
Please see below threads for more information.
Using a variable for the service connection
Azure subscription endpoint ID cannot be provided through a variable in build definition YAML file
You need to pass variable group name to template and on template level apply it on variables scope like this:
parameters:
env: 'DEFAULTVALUE'
subscriptionName: 'DEFAULTVALUE'
subscriptionId: 'DEFAULTVALUE'
variableGroupName: 'DEFAULTVALUE`
jobs:
- deployment: ResourceDeployment
displayName: Deploy Resources ${{ parameters.env }}
environment: ${{ parameters.env }}
variables:
- group: {{ paramaters.variableGroupName }}
strategy:
runOnce:
deploy:
steps:
- task: AzureFileCopy#4
displayName: 'Upload ARM Templates'
inputs:
sourcePath: '$(Pipeline.Workspace)/templates'
azureSubscription: '${{ parameters.subscriptionName }}'
destination: 'azureBlob'
storage: 'my-storage-account'
containerName: 'arm'
name: AzureFileCopy
- task: AzureResourceManagerTemplateDeployment#3
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: '${{ parameters.subscriptionName }}'
subscriptionId: '${{ parameters.subscriptionId }}'
action: 'Create Or Update Resource Group'
resourceGroupName: 'my-resource-group'
location: 'eastus2'
templateLocation: 'URL of the file'
csmFileLink: '$(AzureFileCopy.StorageContainerUri)${{ parameters.env }}/templates/main.json$(AzureFileCopy.StorageContainerSasToken)'
This is similar to this topic:
For security reasons, we only allow you to pass information into templated code via explicit parameters.
https://learn.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops
The means the author of the pipeline using your template needs to commit changes where they explicitly pass the needed info into your template code.
There are some exceptions to this, where the variable is statically defined in the same file or at pipeline compile time, but generally speaking, it’s probably better to use parameters for everything that does not involve system-defined read-only dynamic variable and custom-defined dynamic output variables.
I have to variables groups
QA
PROD
both has isProd variable
and then my template is
parameters:
variableGroupName: 'QA'
jobs:
- job: ResourceDeployment
variables:
- group: ${{ parameters.variableGroupName }}
steps:
- script: echo '$(isProd)'
and main file is
trigger: none
pr: none
stages:
- stage: QA
jobs:
- template: sub-template.yml
parameters:
variableGroupName: 'QA'
- stage: PROD
jobs:
- template: sub-template.yml
parameters:
variableGroupName: 'PROD'
and I got:
and

Resources