I have the following pipeline template that I want to use to conditionally execute stages based on the input parameter stages.
parameters:
- name: dryRun
default: false
type: boolean
- name: path
type: string
default: terraform
- name: stages
type: object
default:
- test
- prod
stages:
- stage:
pool:
vmImage: 'ubuntu-latest'
displayName: "Deploy to test"
condition: in('test', ${{ parameters.stages }})
jobs:
- template: terraform-job.yaml
parameters:
stage: test
path: ${{ parameters.path }}
dryRun: ${{ parameters.dryRun }}
- stage:
pool:
vmImage: 'ubuntu-latest'
displayName: "Deploy to production"
condition: in('prod', '${{ join(', ', parameters.stages) }}')
jobs:
- template: terraform-job.yaml
parameters:
stage: production
path: ${{ parameters.path }}
dryRun: ${{ parameters.dryRun }}
In the example you can see two of the approached I tried (I tried a lot...). The last one (in('prod', '${{ join(', ', parameters.stages) }}')) actually compiles but then the check only works partially as the array gets converted into a single string: 'test,prod' which will fail the in('test', 'test,prod') check.
And the first example (in('test', ${{ parameters.stages }})) is the one I think should work with logical thinking but I get the following error when compiling the template: /terraform-deployment.yml (Line: 19, Col: 16): Unable to convert from Array to String. Value: Array.
So now the question:
How do I check if a string is part of an array that was defined as a parameter?
2022 update
You can now use containsValue:
condition: ${{ containsValue(parameters.stages, 'test') }}
Try contains instead:
condition: contains('${{ join(';',parameters.stages) }}', 'test')
Related
I'm trying to build a new yaml file that reads keyvault secrets based on the parameters at the runtime and declared variables with the condition as per the parameters, but this isn't working.
- name: azure_subscription
displayName: " Select subscription "
type: string
default: "service-connection-dev"
values:
- 'service-connection-dev'
- 'service-connection-sit'
- 'service-connection-tes'
- 'service-connection-prd'
variables:
- ${{ if eq('${{ parameters.azure_subscription }}', 'service-connection-sit') }}:
name: key_vault
value: 'core-kv-sit'
- ${{ if eq('${{ parameters.azure_subscription }}', 'service-connection-dev') }}:
name: key_vault
value: 'core-kv-dev'
stages:
- stage: Validate
${{ if eq(parameters.azure_subscription, 'service-connection-dev') }}:
pool:
name: agent-pool-win-dev
${{ if eq(parameters.azure_subscription, 'service-connection-sit') }}:
pool:
name: agent-pool-win-sit
jobs:
- job: Validate
steps:
- task: AzureKeyVault#2
inputs:
KeyVaultName: "${{variables.key_vault}}"
SecretsFilter: "*"
RunAsPreJob: false
azureSubscription: ${{ parameters.azure_subscription }}
I've tried using variables inside jobs, but that is also not working. Can someone please help?
Also, I'll have to declare 2 more variables as per the parameters input, Is it possible ?
Thanks in advance
- ${{ if eq('${{ parameters.azure_subscription }}', 'service-connection-sit') }}:
You're using a literal value of "${{ parameters.azure_subscription }}" for the left side of the comparison. The comparison should just be parameters.azure_subscription.
So: - ${{ if eq(parameters.azure_subscription, 'service-connection-sit') }}:
How do I do the condition part in the code below under "Approval_Test" stage? I have to generate the condition dynamically , can't hardcode it due to some requirements. The stage will also be generated dynamically based on the parameters param.
"Approval_Test" stage can only be run after all solution1, 2 and 3 are finished and/or skipped
#azure-pipeline.yml
trigger: none
#Package Parameter
parameters:
- name: "params"
type: object
default:
Solution1:
name: "Solution1"
Solution2:
name: "Solution2"
Solution3:
name: "Solution3"
stages:
- ${{ each param in parameters.params }}:
- stage: Deploy_dev_${{ param.value.name }}
jobs:
- template: deploy-dev.yml
- stage: Approval_Test
dependsOn:
- ${{ each param2 in parameters.params }}:
- Deploy_dev_${{ param2.value.name }}
condition: |
#The "each" below would not work and throw errors.
and
(
- ${{ each param2 in parameters.params }}:
in(dependencies.Deploy_dev_${{ param2.value.name }}.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),\
)
jobs:
- deployment: Approval
environment: 'sandbox'
This was a tough nut to crack and I wasn't able to solve it with the each keyword, but please let me present a feasible alternative.
Alternate solution: Job status check functions
Without your requirement of also having the skipped stages the solution would be adding condition: succeeded() to the stage: Approval_Test :
But this doesn't work for a skipped stage.
Not Failed?
Unfortunately there is no 'Skipped' to check from the job status functions, but what if we take the opposite of failed(), not(failed())!?
The works out fine:
#azure-pipeline.yml
trigger: none
#Package Parameter
parameters:
- name: "params"
type: object
default:
Solution1:
name: "Solution1"
Solution2:
name: "Solution2"
Solution3:
name: "Solution3"
stages:
- ${{ each param in parameters.params }}:
- stage: Deploy_dev_${{ param.value.name }}
jobs:
- template: deploy-dev.yml
- stage: skipped # test to simulate a skip from the template
condition: failed()
jobs:
- job: concat
steps:
- ${{ each parameter in parameters.params }}:
- script: echo Deploy_dev_${{ parameter.value.name }}.result
- stage: Approval_Test
condition: not(failed())
dependsOn:
- skipped # test to simulate a skip from the template
- ${{ each param2 in parameters.params }}:
- Deploy_dev_${{ param2.value.name }}
jobs:
- deployment: Approval
environment: 'sandbox'
(Don't) use the equivalent
According to the docs failed() is a equivalent for eq(variables['Agent.JobStatus'], 'Failed').
Would eq(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues', 'Skipped') not be the solution then?
No!
Why I don't understand, but in my tests (above) with the yaml (below) it wouldn't fly.
#azure-pipeline.yml
trigger: none
#Package Parameter
parameters:
- name: "params"
type: object
default:
Solution1:
name: "Solution1"
Solution2:
name: "Solution2"
Solution3:
name: "Solution3"
stages:
- ${{ each param in parameters.params }}:
- stage: Deploy_dev_${{ param.value.name }}
jobs:
- template: deploy-dev.yml
- stage: skipped # test to simulate a skip from the template
condition: failed()
jobs:
- job: concat
steps:
- ${{ each parameter in parameters.params }}:
- script: echo Deploy_dev_${{ parameter.value.name }}.result
- stage: Approval_Test
condition: in(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues', 'Skipped')
dependsOn:
- skipped # test to simulate a skip from the template
- ${{ each param2 in parameters.params }}:
- Deploy_dev_${{ param2.value.name }}
jobs:
- deployment: Approval
environment: 'sandbox'
Conclusion
If not(failed()) works for you, I advice you to use that.
I have two Azure Devops pipelines: 'Starter' and 'Processing'. 'Starter' triggers 'Processing' and passes some parameters to it.
Starter:
trigger: none
pool:
vmImage: 'windows-2019'
stages:
- stage: A
jobs:
- template: Processing.yml
parameters:
products: $(Products)
creds: $(Creds)
Processing:
parameters:
- name: products
type: object
default: []
- name: creds
default: ''
jobs:
- ${{ each product in parameters.products }}:
- task: PowerShell#2
displayName: Importing ${{ product }} solution
inputs:
targetType: 'inline'
script: |
#Code
Key detail here is opportunity to loop through 'products' variable (each product in parameters.products), which must be setted in 'Starter' variables:
In other words, starting my pipeline I must pass list of products as 'string' and then loop through this list in second pipeline. 'Is it generally possible? Maybe products should be another type? I tried some work around but didn't get appropriate solution:
- job: Prepare_Products_Array
steps:
- task: PowerShell#2
inputs:
targetType: 'inline'
script: |
# Write your PowerShell commands here.
$productsArray = []
$productsArray = $(Products)
$productsArray = $productsArray.Split(',')
Write-Host ("##vso[task.setvariable variable=productsArray;]$productsArray")
- template: Processing.yml
parameters:
products: $env:productsArray
Exception:
From your yaml sample, you are defining the variable in YAML Pipeline UI and using parameters in YAML Template.
In this case, the variables defined on the UI will be assigned at runtime, but the parameters and expressions in the YAML template will be expanded at compile time.
Therefore, YAML UI variables cannot be passed to the Pipeline YAML Template.
And it will show the error:
Expected a...... Actual value $(Product)
This means that the pipeline variable not expanded at compile time.
I am afraid that there is no such method can pass the UI Pipeline variable to YAML Template.
Here are the workarounds:
Method 1 : You can use parameters in Starter yaml to pass the Object type parameters to YAML template.
Starter:
trigger: none
parameters:
- name: products
type: object
default: []
- name: creds
default: ''
pool:
vmImage: 'windows-2019'
stages:
- stage: A
jobs:
- template: Processing.yml
parameters:
products: ${{ parameters.products }}
creds: ${{ parameters.creds }}
Processing:
parameters:
- name: products
type: object
default: []
- name: creds
default: ''
jobs:
- job: test
steps:
- ${{ each product in parameters.products }}:
- task: PowerShell#2
displayName: Importing ${{ product }} solution
inputs:
targetType: 'inline'
script: |
echo ${{ product }}
Result: You can input the value when you run the pipeline.
Method2: You need to define the variable in Starter pipeline and change the products parameters as String type. Then you can use the expression - ${{ each product in split(parameters.products, ',')}}: to split the string.
Starter:
pool:
vmImage: 'windows-2019'
variables:
products: '1,2,3'
creds: test
stages:
- stage: A
jobs:
- template: Processing.yml
parameters:
products: ${{ variables.products }}
creds: ${{ variables.creds }}
Processing:
parameters:
- name: products
type: string
default: ''
- name: creds
default: ''
jobs:
- job: test
steps:
- ${{ each product in split(parameters.products, ',')}}:
- task: PowerShell#2
displayName: Importing ${{ product }} solution
inputs:
targetType: 'inline'
script: |
echo ${{ product }}
Essentially I am trying to use a if else logic inside a yaml template instead of the caller pipeline.
I have following two pipelines azure-caller.yml and template.yaml
azure-caller.yml
parameters:
- name: test
displayName: 'select true or false'
values:
- true
- false
variables:
- name: test-true
${{ if eq(parameters.test, 'true') }}:
value: false
${{ elseif eq(parameters.test, 'false') }}:
value: true
stages:
- template: job-templates/template.yml
parameters:
testrue: $(test-true)
template.yml
parameters:
testrue: test_true
stages:
- stage: A
jobs:
- job: JA
steps:
- script: |
echo "Reverted value is" ${{ parameters.testrue }}
name: DetermineResult
How can I move the if else logic in the template.yml instead of azure-caller.yml? Your input will be helpful. thx
Below pipeline should do the trick with a limitation that the scope of variable test-true in the template will be limited only for stage: A
azure-caller.yml
parameters:
- name: test
displayName: 'select true or false'
values:
- true
- false
stages:
- template: job-templates/template.yml
parameters:
testrue: ${{ parameters.test }}
template.yml
parameters:
testrue: default
stages:
- stage: A
variables:
- name: test-true
${{ if eq(parameters.testrue, 'true') }}:
value: NewValueForTrueParam
${{ elseif eq(parameters.testrue, 'false') }}:
value: NewValueForFalseParam
jobs:
- job: JA
steps:
- script: |
echo "Echo value is" $(test-true)
Result
I have multiple resusable yaml templates to build my project using Azure DevOps. I want to let the default template values when I don't want to override it. The problem I have is when I nest multiple templates, it's not working as I expected, here is an example of my issue:
task_template1.yml:
parameters:
foo: 'data'
steps:
- task: Bash#3
inputs:
targetType: 'inline'
script: echo ${{ parameters.foo }}
Inside a job_template.yml I call this task_template1.yml like this:
job_template.yml:
parameters:
fooTemplate: 'data'
jobs:
- job:
steps:
- template: task_template1.yml
parameters:
${{ if variables[parameters.fooTemplate] }}:
foo: ${{ parameters.fooTemplate }}
And then my global template:
stage_build.yml:
stages:
- stage: Build
jobs:
- template: job_template.yml
parameters:
fooTemplate: 'hello'
So in this case, by default I expect that my variable fooTemplate will set the value of the variable foo inside my task_template1.yml to hello because I specify it.
But it's not the case, because it looks like ${{ if variables[parameters.fooTemplate] }}: is evaluated before the build start so foo is set to data instead of hello. Conversely if I would like to keep the default value of fooTemplate (data) I just want to not specify it like this:
stages:
- stage: Build
jobs:
- template: job_template.yml
It looks like a problem of nested templates. How can I do to fit this two rules? Feel free to ask me some more details if needed.
EDIT:
I always have this message : 'foo has no value: undefined'
Other case that I have, instead of a simple parameter:
parameters:
foo:'data'
foo1:'data1'
steps:
- task: Bash#3
inputs:
targetType: 'inline'
script: echo ${{ parameters.foo }}
So for the job I have:
parameters:
fooTemplate:
foo:'data'
foo1:'data1'
jobs:
- job:
steps:
- template: task_template1.yml
parameters:
${{ if ne(parameters.fooTemplate.foo, ' ') }}:
foo: ${{ parameters.fooTemplate.foo }}
${{ if ne(parameters.fooTemplate.foo1, ' ') }}:
foo: ${{ parameters.fooTemplate.foo1 }}
And then same case if I don't specify the foo and foo1 value I have the undefined error, instead of taking the default values:
stages:
- stage: Build
jobs:
- template: job_template.yml
I think something is wrong with your syntax here:
parameters:
${{ if variables[parameters.fooTemplate] }}:
foo: ${{ parameters.fooTemplate }}
Try this way(according to this):
parameters:
${{ if eq(parameters.fooTemplate, true) }}:
foo: ${{ parameters.fooTemplate }}
It works on my side.
Notes:
You can have something like this:
parameters:
fooTemplate: 'data'
jobs:
- job:
steps:
- template: task_template1.yml
parameters:
${{ if ne(parameters.fooTemplate, ' ') }}:
foo: ${{ parameters.fooTemplate }}
Now if you specify 'hello' in stage_build.yml, the task_template1.yml will output hello. And if you don't specify the parameter in stage_build.yml like this:
pool:
vmImage: 'ubuntu-latest'
jobs:
- template: job_template.yml
It will output the default data. Here're more details about Expressions and Conditions.