Using variables in DevOps YAML Pipelines - azure-devops

I'm trying to add some conditional logic to my Azure DevOps pipeline to perform actions based on if there are pending changes in the Git repository. I've created a PowerShell script to check for changes and set a variable, which is working:
$gitStatus = (git status --porcelain) | Out-String
if ($gitStatus) {
Write-Host "##vso[task.setvariable variable=changes;]true"
Write-Host "##[debug]Changes found"
} else {
Write-Host "##vso[task.setvariable variable=changes;]false"
Write-Host "##[debug]No changes found"
}
I can then output the resulting value of "changes" in my pipeline as follows:
- script: echo Output - $(changes)
This returns "Output - true" as expected
If I then add the following to my YAML...
- ${{ if eq(variables.changes, true) }}:
- script: echo Changes = True
- ${{ else }}:
- script: echo Changes = False
I always receive "Changes = False"
Any help would be gratefully received.

Thanks to input from 4c74356b41 I've come up with the following solution:
- script: echo Changes = True
condition: eq(variables.changes, true)
- script: echo Changes = False
condition: ne(variables.changes, true)
The final pipeline will call templates with the required actions to be performed based on the result of the check, but the above works well enough to prove the concept.

Related

azure devops - yaml pipeline stage to depend on other pipeline completion

I have a pipeline 'A' which has two stages- dev, prod. After dev environment finishes I want prod stage to be triggered only if pipeline 'B' is ran successfully. I want stage 'prod' of pipeline 'A' to be dependent on pipeline 'B'. Is this feasible?
You can get Pipeline B result in stage dev (link here), and set it as variable, in the prod stage, evaluate the variable value to determine the stage to run or not(link here).
Code sample as below:
stages:
- stage: Dev
jobs:
- job: DevJob
steps:
- task: PowerShell#2
name: GetpipelineBresult
inputs:
targetType: 'inline'
script: |
$url = "https://dev.azure.com/{organization}/{pipelineBProject}/_apis/build/builds?definitions={definitionid}&api-version=5.1"
$personalToken = "$(PAT)"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($personalToken)"))
$header = #{authorization = "Basic $token"}
$buildPipeline= Invoke-RestMethod -Uri $url -Headers $header -Method Get
$BuildResult= $buildPipeline.value.result | Select-Object -first 1
Write-Host This is Build Result: $BuildResult
echo "##vso[task.setvariable variable=Buildresult;isOutput=true]$BuildResult"
- stage: Prod
condition: eq(dependencies.Dev.outputs['DevJob.GetpipelineBresult.Buildresult'], 'succeeded')
dependsOn: Dev
jobs:
- job:
steps:
- script: echo job Prod
Pipeline B result: succeeded
Pipeline B result: not succeeded(cancel, failed..)
1.Intasll this Azure DevOps Extension
2.In your Dev stage, add Trigger Build task to ensure you could trigger pipeline B and check the latest build result of pipeline B.
3.Create the Generic Service Connection.
4.Use Invoke REST API in Environment Approvals and checks.
API: GET https://dev.azure.com/{organization}/{project}/_apis/build/latest/{definition}?api-version=6.0-preview.1
5.After check pass, second stage will depend on the success build of Pipeline B.
trigger:
- none
stages:
 - stage: Dev
   jobs:
    - job: CI
      pool:
        vmImage: windows-latest
      steps:
        - task: TriggerBuild#4
          inputs:
            definitionIsInCurrentTeamProject: true
            buildDefinition: 'PipelineB'
            queueBuildForUserThatTriggeredBuild: true
            ignoreSslCertificateErrors: false
            useSameSourceVersion: false
            useCustomSourceVersion: false
            useSameBranch: true
            waitForQueuedBuildsToFinish: false
            storeInEnvironmentVariable: false
            authenticationMethod: 'Personal Access Token'
            password: 'PAT'
            enableBuildInQueueCondition: false
            dependentOnSuccessfulBuildCondition: false
            dependentOnFailedBuildCondition: false
            checkbuildsoncurrentbranch: false
            failTaskIfConditionsAreNotFulfilled: false
        
 - stage: Prod
   dependsOn: Dev
   jobs:
    - deployment: CD
      environment: {EnvironmentName}
      pool:
        vmImage: windows-latest
      strategy:
        runOnce:
          deploy:
              steps:
                - task: CmdLine#2
                  inputs:
                    script: |
                      echo Write your commands here
                      
                      echo Hello world

Getting `Argument list too long` in GitHub Actions

I am following HashiCorp's learning guide on how to set up GitHub Actions and terraform. All is running great besides the step to update the PR with the Terraform Plan.
I am hitting the following error:
An error occurred trying to start process '/home/runner/runners/2.287.1/externals/node12/bin/node' with working directory '/home/runner/work/ccoe-aws-ou-scp-manage/ccoe-aws-ou-scp-manage'. Argument list too long
The code I am using is:
- uses: actions/github-script#0.9.0
if: github.event_name == 'pull_request'
env:
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`${process.env.PLAN}\`\`\`
</details>
*Pusher: #${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;
github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
A clear COPY/Paste from the docs: https://learn.hashicorp.com/tutorials/terraform/github-actions
I have tried with
actions/github-script version 5 and 6 and still the same problem, But when I copy paste the plan all is great. If I do not use the output variable and use some place holder text for the body all is working great. I can see that the step.plan.outputs.stdout is Ok if I print only that.
I will be happy to share more details if needed.
I also encountered a similar issue.
I seem github-script can't give to argument for too long script.
reference:
https://github.com/robburger/terraform-pr-commenter/issues/6#issuecomment-826966670
https://github.community/t/maximum-length-for-the-comment-body-in-issues-and-pr/148867
my answer:
- name: truncate terraform plan result
run: |
plan=$(cat <<'EOF'
${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }}
EOF
)
echo "${plan}" | grep -v 'Refreshing state' >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: create comment from plan result
uses: actions/github-script#0.9.0
if: github.event_name == 'pull_request'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`\n
${ process.env.PLAN }
\`\`\`
</details>
*Pusher: #${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ inputs.TF_WORK_DIR }}\`, Workflow: \`${{ github.workflow }}\`*`;
github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})```
Based on #Zambozo's hint in the comments, this worked for me great:
- name: Terraform Plan
id: plan
run: terraform plan -no-color -input=false
- name: generate random delimiter
run: echo "DELIMITER=$(uuidgen)" >> $GITHUB_ENV
- name: truncate terraform plan result
run: |
echo "PLAN<<${{ env.DELIMITER }}" >> $GITHUB_ENV
echo '[maybe truncated]' >> $GITHUB_ENV
tail --bytes=10000 <<'${{ env.DELIMITER }}' >> $GITHUB_ENV
${{ format('{0}{1}', steps.plan.outputs.stderr, steps.plan.outputs.stdout) }}
${{ env.DELIMITER }}
echo >> $GITHUB_ENV
echo "${{ env.DELIMITER }}" >> $GITHUB_ENV
- name: post plan as sticky comment
uses: marocchino/sticky-pull-request-comment#v2
with:
header: plan
message: |
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
<details><summary>Show Plan</summary>
```
${{ env.PLAN }}
```
</details>
Notably GitHub does have an upper limit on comment size, so this only displays the last 10kB of the plan (showing the summary and warnings).
This also implements secure heredoc delimiting to avoid malicious output escaping.
Also note that the empty lines before and after the triplebacktics in the message are significant to avoid destroying the formatting.

Resolving Predefined Agent Variables in Runtime Expressions

I have the following scenario, it is simplified for the sake of brevity, but outlines my problem.
I have a 2 Job pipeline.
BreakMatrix Job: A job that runs on an AdminPool and outputs 2 variables with the following names ENV1 and ENV2. The names are important because each of them matches the name of an Environment running in a separate MachinePool VM deployment pool.
Upgrade Job: A deployment job that depends on the BreakMatrix job and that runs on a VM MachinePool, with a tag that will select ENV1 and ENV2 environments.
I am trying to pass in one of the variables to each of the corresponding Environments:
- job: BreakMatrix
pool: AdminPool
steps:
- checkout: none
- powershell: |
$result1 = #{"Hostname" = "Env1Value"}
$result2 = #{"Hostname" = "Env2Value"}
Write-Host "##vso[task.setvariable variable=ENV1;isOutput=true;]$result1"
Write-Host "##vso[task.setvariable variable=ENV2;isOutput=true;]$result2"
name: outputter
- deployment: Upgrade
dependsOn: BreakMatrix
variables:
agentName: $(Environment.ResourceName)
agentName2: $(Agent.Name)
formatted: $[ format('outputter.{0}', variables['agentName']) ]
result1: $[ dependencies.BreakMatrix.outputs[format('outputter.{0}', variables['agentName'])] ]
result2: $[ dependencies.BreakMatrix.outputs[format('outputter.{0}', variables['agentName2'])] ]
result3: $[ dependencies.BreakMatrix.outputs[format('outputter.{0}', variables['Agent.Name'])] ]
hardcode: $[ dependencies.BreakMatrix.outputs['outputter.ENV2'] ]
json: $[ convertToJson(dependencies.BreakMatrix.outputs) ]
environment:
name: MachinePool
resourceType: VirtualMachine
tags: deploy-dynamic
strategy:
rolling:
preDeploy:
steps:
- powershell: |
echo 'Predeploy'
echo "agentName: $(agentName)"
echo "agentName2: $(agentName2)"
echo "env: $(Environment.ResourceName)"
echo "formatted: $(formatted)"
echo "harcode: $(harcode)"
echo "result1: $(result1)"
echo "result2: $(result2)"
echo "result3: $(result3)"
echo "json: $(json)"
deploy:
steps:
- powershell: |
echo 'Deploy'
Output for ENV2 pre-deploy step:
Predeploy
agentName: ENV2
agentName2: ENV2
env: ENV2
formatted: outputter.ENV2
hardcode: {
Hostname:Env2Value}
result1:
result2:
result3:
json: {
outputter.ENV2:
\"Hostname\":\"Env2Value\"
}
If i try to use a predefined variable in a dependency expression they don't seem to properly resolve, but if i just simply map them to a variable / format them they work.
Note: The environment names are actually dynamically calculated before these jobs run so I cannot use parameters or static/compile time variables.
Any suggestions on how to pass in only the relevant variable to each of the environments ?
I got confirmation on the developer community that this is not possible.
Relevant part:
But I am afraid that the requirement couldn’t be achieved.
The reason is that the dependencies.xx.outputs expression can’t read
the variable(variables['Agent.Name']) and format expression value
defined in the YAML pipeline.
It now only supports hard-coding the name of the variable to get the
corresponding job variable value.

Azure devops pass build parameters through Rest API

I'm trying to pass a parameters through the build rest API using jira, but it doesn't override the parameter.
Pipeline:
parameters:
- name: "Testplan"
type: string
default: "NoPlanDefined"
stage: Test
jobs:
- job: Testing_And_Transfer
- task: PowerShell#2
displayName: "Testing API Call"
inputs:
targetType: 'filepath'
filePath: './script/Jira.ps1'
arguments:
-Jira_id ${{ parameters.Testplan }}
Jira.ps1 content:
Param(
[string]$Jira_id = "no ID"
)
#-----------------------Jira API--------------------------
echo 'This is a test \n ID: '
echo $Jira_id
My rest command is setup like so:
URL: https://dev.azure.com/{My corp}/MyHello/_apis/build/builds?api-version=6.0
Body:
{
"definition": {
"id": 1
},
"parameters": "{ \"Testplan\":\"Postman\" }"
}
When using the trigger, the ps1 return NoPlanDefined as expected.
When using a manual trigger and changing the parameter, the parameter
get changed as expected.
When trying to change the parameter through
the Rest api, Testplan is empty instead of Postman.
I'm I doing something wrong with the REST API?
That's because those are not parameters, despite the name used in the REST call. They are run-time variables, which behave differently and are available at a different scope than parameters.
There is a different API that allows you to specify templateParameters: https://learn.microsoft.com/en-us/rest/api/azure/devops/pipelines/runs/run-pipeline?view=azure-devops-rest-6.1
If you are familiar with PowerShell you can use the AzurePipelinesPS module and the command below to pass parameters to pipelines when invoking them.
$id = '2' # your pipeline id
$templateParameters = #{
Testplan = 'myTestPlan' # your parameter and value
}
Invoke-APPipeline -Instance https://dev.azure.com -Collection 'yourCollectionName' -Project 'yourProjectName' -ApiVersion '6.0-preview.1' -PipelineId $id -TemplateParameters $templateParameters
The module supports "sessions" to limit the number of required parameters. See the module's github page on how to create a session.

AZP: Is there a best practice to be able to "namespace" script tasks in yaml templates for usage of variables?

In Azure Pipelines: my main problem is, if I create a yml template and have some logic inside that template in a script task where I want to set a variable, i need the
name: "pseudonamespace" to reference that variable further down in that template via
$(pseudonamespace.variablename)
An example, where the script part does nothing overtly useful, but should demonstrate my problem:
mytemplate.yml:
parameters:
- name: isWindowsOnTarget
type: boolean
default: true
steps:
- script: |
if [ "${{lower(parameters.isWindowsOnTarget)}}" == "true" ]; then
delimiter="\\"
else
delimiter="/"
fi
echo "##vso[task.setvariable variable=myCoolVariable;isOutput=true]$delimiter"
name: MyFakeNameSpace
...
- task: SomeTask#0
inputs:
myInput: $(MyFakeNameSpace.myCoolVariable)
This codeblock works; but only, if, in a job, I only instanciate it once:
- template: mytemplate.yml#templates
parameters:
isWindowsOnTarget: true
If I would need that template twice, differently parameterized, I get the error that the name of the script block needs to be unique.
Is there any useful possibility I'm not currently thinking about other than to have an extra parameter for the template that I could basically just call "UniqueNamespace"?
There is no much space to move. Your task needs a unique name as later as you mention for output parameters it works like a namespace. So the best and the only way you have is to provide another parameter which would be task name.
parameters:
- name: isWindowsOnTarget
type: boolean
default: true
- name: taskName
type: string
steps:
- script: |
if [ "${{lower(parameters.isWindowsOnTarget)}}" == "true" ]; then
delimiter="\\"
else
delimiter="/"
fi
echo "##vso[task.setvariable variable=myCoolVariable;isOutput=true]$delimiter"
name: ${{ parameters.taskName }}
...
- task: SomeTask#0
inputs:
myInput: $(MyFakeNameSpace.myCoolVariable)
and then:
- template: mytemplate.yml#templates
parameters:
isWindowsOnTarget: true
taskName: MyFakeNameSpace
- template: mytemplate.yml#templates
parameters:
isWindowsOnTarget: true
taskName: MyFakeNameSpace2
In fact when you do not provide a name Azure DevOps assign a unique name. However, in this way you don't know the name till runtime.