In Azure DevOps REST Api how to get the build variables of the given build? - azure-devops-rest-api

Using the REST API I can easily obtain an object corresponding to the given build. But at the time when that build was queued it was given build variables. Some were set at queue time, some were inherited from the build definition.
So far I failed to figure out how I can assess these build variables. The build object does not seem to contain them.
I can clearly see the top level template parameters in the parameters property. But where are the build variables?
Edit 1
We use the on-prem version of Azure DevOps 2020. The highest api version it supports is 6.1-preview and the "Runs - Get" in that version does not seem to return much. Please, observe:
C:\> Invoke-RestMethod "$TfsInstanceUrl/DFDevOps/_apis/pipelines/$($b.definition.id)/runs/$($b.id)?api-version=6.1-preview" -UseDefaultCredentials
_links : #{self=; web=; pipeline.web=; pipeline=}
pipeline : #{url=https://tdc1tfsapp01.dayforce.com/tfs/DefaultCollection/d85566a3-9e95-4891-9fd8-42750a0bc250/_apis/pipelines/8781?revision=19; id=8781; revision=19;
name=PRBuild Stress Test; folder=\}
state : completed
result : succeeded
createdDate : 2022-02-11T15:57:07.4271331Z
finishedDate : 2022-02-12T00:46:22.6090285Z
url : https://tdc1tfsapp01.dayforce.com/tfs/DefaultCollection/d85566a3-9e95-4891-9fd8-42750a0bc250/_apis/pipelines/8781/runs/1399783
resources : #{repositories=}
id : 1399783
name : 20220211.2
C:\>
Does it mean the variables are only available in Azure DevOps Services?

You can try using the API "Runs - Get" to get the build.
Normally, in the response body of this API, you will see the pipeline variables are listed in the variables object.

Related

PullRequest Build Validation with Jenkins and OnPrem Az-Devops

First off the setup in question:
A Jenkins Instance with several build nodes and on prem Azure-Devops server containing the Git Repositories.
The Repo in question is too large to always build on push for all branches and all devs, so a small workaround was done:
The production branches have a polling enabled twice a day (because of testing duration which is handled downstream more builds would not help with quality)
All other branches have their automated building suppressed. They still can start it manually for Builds/Deployments/Unittests if they so choose.
The jenkinsfile has parameterization for which platforms to build, on prod* all the platforms are true, on all other branches false.
This helps because else the initial build of a feature branch would always build/deploy locally all platforms which would take too much of a load on the server infrastructure.
I added a service endpoint for Jenkins in the Azure Devops, added a Buildvalidation .yml - this basically works because when I call the sourcebranch of the pull request with the merge commitID i added a parameter
isPullRequestBuild which contains the ID of the PR.
snippet of the yml:
- task: JenkinsQueueJob#2
inputs:
serverEndpoint: 'MyServerEndpoint'
jobName: 'MyJob'
isMultibranchJob: true
captureConsole: true
capturePipeline: true
isParameterizedJob: true
multibranchPipelineBranch: $(System.PullRequest.SourceBranch)
jobParameters: |
stepsToPerform=Build
runUnittest=true
pullRequestID=$(System.PullRequest.PullRequestId)
Snippet of the Jenkinsfile:
def isPullRequest = false
if ( params.pullRequestID?.trim() )
{
isPullRequest = true
//do stuff to change how the pipeline should react.
}
In the jenkinsfile I look whether the parameter is not empty and reset the platforms to build to basically all and to run the unittests.
The problem is: if the branch has never run, Jenkins does not already know the parameter in the first run, so it is ignored, building nothing, and returning with 0 because "nothing had to be done".
Is there any way to only run the jenkins build if it hasnt run already?
Or is it possible to get information from the remote call if this was the build with ID 1?
The only other thing would be to Call the Jenkins via web api and check for the last successful build, but in that case I would have have the token somewhere stored in source control.
Am I missing something obvious here? I dont want to trigger the feature branch builds to do nothing more than once, because Devs could lose useful information about their started builds/deployments.
Any ideas appreciated
To whom it may concern with similar problems:
In the end I used the following workaround:
The Jenkins Endpoint is called via a user that only is used for automated builds. So, in case that this user triggered the build, I set everything to run a Pull Request Validation, even if it is the first build. Along the lines of
def causes = currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause')
if (causes != null)
{
def buildCauses= readJSON text: currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause').toString()
buildCauses.each
{
buildCause ->
if (buildCause['userId'] == "theNameOfMyBuildUser")
{
triggeredByAzureDevops = true
}
}
}
getBuildcauses must be allowed to run by a Jenkins Admin for that to work.

Launching composed task built by DSL from stream application

Every example I've seen (task-launcher sink and triggertask source ) shows how to launch the task defined by uri attribute.
My tasks definitions look like this :
sampleTask <t2: timestamp || t1: timestamp>
sampleTask-t1 timestamp
sampleTask-t2 timestamp
sampleTaskRunner composed-task-runner --graph=sampleTask
My question is how do I launch the composed task runner (sampleTaskRunner, defined by DSL) from stream application.
Thanks
UPDATE
I ended up with the below solution that triggers task using SCDF REST API :
composedTask definition :
<timestamp || mySampleTask>
Stream definition :
http | httpclient | log
Deployment properties :
app.http.port=81
app.httpclient.body=name=composedTask&arguments=--increment-instance-enabled=true
app.httpclient.http-method=POST
app.httpclient.url=http://localhost:9393/tasks/executions
app.httpclient.headers-expression={'Content-Type':'application/x-www-form-urlencoded'}
Though it's easy to implement http sink component, would be great if stream application starters will provide one out of the box.
Another concern I have is about discovering the SCDF REST URL when deployed in distributed environment.
Here's a quick take from one of the SCDF's R&D team members (Glenn Renfro).
stream create foozer --definition "trigger --fixed-delay=5 | tasklaunchrequest-transform --uri=maven://org.springframework.cloud.task.app:composedtaskrunner-task:1.1.0.BUILD-SNAPSHOT --command-line-arguments='--graph=sampleTask-t1||sampleTask-t2 --increment-instance-enabled=true --spring.datasource.url=jdbc:mariadb://localhost:3306/test --spring.datasource.username=root --spring.datasource.password=password --spring.datasource.driverClassName=org.mariadb.jdbc.Driver' | task-launcher-local" --deploy
In the foozer stream definition,
1) "trigger" source happens to trigger an upstream event every 5s
2) "tasklaunchrequest-transform" processor takes a few arguments; more specifically, it uses "composedtaskrunner-task:1.1.0.BUILD-SNAPSHOT" to launch a composed-task graph (i.e., sampleTask-t1||sampleTask-t2)
3) Pay attention to --increment-instance-enabled. This was recently added to CTR application and this provides the ability to re-launch a composed-task in a recurring cadence
4) Since the CTR and SCDF must share the same database, we are also passing datasource properties as command-line args. (SCDF-server is already started with the same datasource credentials)
Hope this helps.
Lastly, we will add a sample to the reference guide via: spring-cloud/spring-cloud-dataflow#1780

Are TFS Build Agent User Capabilities' Values Obtainable Within Build Steps?

I'm trying to write a build step within TFS that relies on knowing where the Build agent has nuget.exe stored (the standard nuget-install step mucks around with the order of arguments in a way that breaks build execution, so I want to run the exe myself using one of the batch/shell/ps steps).
It would seem that setting up a capability on the Build Agent with that path would make sense, but I cannot seem to reference the value in any of my build steps, and I cannot find anything helpful on MSDN.
I'm expecting it to be something like $(Env.MyUserCapability), but it never resolves to the value.
Is it possible to retrieve a capability value within a build step? And if so, how do you do it? And if not, what is a viable alternative?
The user-defined capabilities are metadata only. But you can set a global environment variable (e.g. NUGET) and set that to a path to a nuget.exe, when you restart the agent, the machine-wide environment is then discovered as capability and you can then use it.
If you are writing a custom task, you can also add a nuget.exe to the task that will be downloaded to the executing agent.
UPDATE: I made a public extension out of this.
UPDATE: this works in Azure DevOps 2019.
In TFS 2018u1, the following works:
Import-Module "Microsoft.TeamFoundation.DistributedTask.Task.Common"
Import-Module "Microsoft.TeamFoundation.DistributedTask.Task.Internal"
Add-Type -Assembly "Microsoft.TeamFoundation.DistributedTask.WebApi"
$VSS = Get-VssConnection -TaskContext $distributedTaskContext
$AgentCli = $VSS.GetClient([Microsoft.TeamFoundation.DistributedTask.WebApi.TaskAgentHttpClient])
$AgentConfig = Get-Content "$Env:AGENT_HOMEDIRECTORY\.agent" -Raw | ConvertFrom-Json
$Agent = $AgentCli.GetAgentAsync($AgentConfig.PoolId, $Env:AGENT_ID, $TRUE, $FALSE, $NULL, $NULL, [System.Threading.CancellationToken]::None).GetAwaiter().GetResult()
if($Agent.UserCapabilities.MyCapability)
{
Write-Host "Got the capability!";
}
The long string of default arguments ending with CancellationToken::None is for compatibility with Powershell 4. PS4 doesn't support default values for value-typed method parameters, PS5 does.
This snippet does something very questionable - it relies on the location and the structure of the agent configuration file. This is fragile. The problem is that the GetAgentAsync method requires both pool ID and the agent ID, and the former is not exposed in the environment variables. A slightly less hackish approach would check all pools and find the right one by the agent ID:
$Pools = $AgentCli.GetAgentPoolsAsync($NULL, $NULL, $NULL, $NULL, $NULL, [System.Threading.CancellationToken]::None).GetAwaiter().GetResult()
$Demands = New-Object 'System.Collections.Generic.List[string]'
foreach($Pool in $Pools)
{
$Agent = $AgentCli.GetAgentsAsync($Pool.ID, $Env:AGENT_NAME, $TRUE, $FALSE, $NULL, $Demands, $NULL, [System.Threading.CancellationToken]::None).Result
if($Agent -and $Agent.Id -eq $Env:AGENT_ID)
{
Break
}
}
This relies on another undocumented implementation detail, specifically that agent IDs are globally unique. This seems to hold as late as TFS 2018, but who knows.
When you employ the $distributedTaskContext, the task is connecting back to TFS with an artificial user identity, "Project Collection Build Service" (not with the agent service account). There's one user like that in each collection, they're distinct. In order to allow tasks running in releases in a collection to query the agent for user capabilities, you need to grant the Reader role to the relevant pool(s) (or all pools) to the user account called "Project Collection Build Service (TheCollectionName)" from that collection.
It also looks like some actions also grant an implicit Reader role on a pool to the task identity.
Alternatively, you can construct a VssConnection from scratch with Windows credentials, and grant the agent account(s) Reader role on the pool(s).

build definition from vsts returning zero

I have two build definitions in my project but its returning zero build definition. Below is my code get the build definitions. Can anyone help?
// Get a reference to a build service
IBuildServer buildserver = (IBuildServer)tfs.GetService(typeof(IBuildServer));
IBuildDefinition[] buildDefinitions = buildserver.QueryBuildDefinitions(project);
foreach (IBuildDefinition buildDefinition in buildDefinitions)
{
string buildDefinitionName = buildDefinition.Name;
Console.WriteLine(buildDefinitionName);
Console.WriteLine(buildDefinition.ProcessParameters.ToString());
}
There is nothing wrong with your code. I have tested with your code and get XAML build definitions successfully. Just make sure you specify the correct team project to QueryBuildDefinitions(String).
.Net API can only get XAML build definitions. If you use new build definitions, you are not able to get the build definitions from a .net api.
It's suggested use REST API to get Build Definitions. REST API can get both XAML build definitions and new build definitions.
REST API to get a list of build definitions:
GET https://{instance}/DefaultCollection/{project}/_apis/build/definitions?api-version={version}[&name={string}][&type={string}]

Jenkins, how to check regressions against another job

When you set up a Jenkins job various test result plugins will show regressions if the latest build is worse than the previous one.
We have many jobs for many projects on our Jenkins and we wanted to avoid having a 'job per branch' set up. So currently we are using a parameterized build to build eg different development branches using a single job.
But that means when I build a new branch any regressions are measured against the previous build, which may be for a different branch. What I really want is to measure regressions in a feature branch against the latest build of the master branch.
I thought we should probably set up a separate 'master' build alongside the parameterized 'branches' build. But I still can't see how I would compare results between jobs. Is there any plugin that can help?
UPDATE
I have started experimenting in the Script Console to see if I could write a post-build script... I have managed to get the latest build of master branch in my parameterized job... I can't work out how to get to the test results from the build object though.
The data I need is available in JSON at
http://<jenkins server>/job/<job name>/<build number>/testReport/api/json?pretty=true
...if I could just get at this data structure it would be great!
I tried using JsonSlurper to load the json via HTTP but I get 403, I guess because my script has no auth session.
I guess I could load the xml test results from disk and parse them in my script, it just seems a bit stupid when Jenkins has already done this.
I eventually managed to achieve everything I wanted, using a Groovy script in the Groovy Postbuild Plugin
I did a lot of exploring using the script console http://<jenkins>/script and also the Jenkins API class docs are handy.
Everyone's use is going to be a bit different as you have to dig down into the build plugins to get the info you need, but here's some bits of my code which may help.
First get the build you want:
def getProject(projectName) {
// in a postbuild action use `manager.hudson`
// in the script web console use `Jenkins.instance`
def project = manager.hudson.getItemByFullName(projectName)
if (!project) {
throw new RuntimeException("Project not found: $projectName")
}
project
}
// CloudBees folder plugin is supported, you can use natural paths:
project = getProject('MyFolder/TestJob')
build = project.getLastCompletedBuild()
The main test results (jUnit etc) seem to be available directly on the build as:
result = build.getTestResultAction()
// eg
failedTestNames = result.getFailedTests().collect{ test ->
test.getFullName()
}
To get the more specialised results from eg Violations plugin or Cobertura code coverage you have to look for a specific build action.
// have a look what's available:
build.getActions()
You'll see a list of stuff like:
[hudson.plugins.git.GitTagAction#2b4b8a1c,
hudson.scm.SCMRevisionState$None#40d6dce2,
hudson.tasks.junit.TestResultAction#39c99826,
jenkins.plugins.show_build_parameters.ShowParametersBuildAction#4291d1a5]
These are instances, the part in front of the # sign is the class name so I used that to make this method for getting a specific action:
def final VIOLATIONS_ACTION = hudson.plugins.violations.ViolationsBuildAction
def final COVERAGE_ACTION = hudson.plugins.cobertura.CoberturaBuildAction
def getAction(build, actionCls) {
def action = build.getActions().findResult { act ->
actionCls.isInstance(act) ? act : null
}
if (!action) {
throw new RuntimeException("Action not found in ${build.getFullDisplayName()}: ${actionCls.getSimpleName()}")
}
action
}
violations = getAction(build, VIOLATIONS_ACTION)
// you have to explore a bit more to find what you're interested in:
pylint_count = violations?.getReport()?.getViolations()?."pylint"
coverage = getAction(build, COVERAGE_ACTION)?.getResults()
// if you println it looks like a map but it's really an Enum of Ratio objects
// convert to something nicer to work with:
coverage_map = coverage.collectEntries { key, val -> [key.name(), val.getPercentageFloat()] }
With these building blocks I was able to put together a post-build script which compared the results for two 'unrelated' build jobs, then using the Groovy Postbuild plugin's helper methods to set the build status.
Hope this helps someone else.

Resources