Upate Release Definition using Azure DevOps REST API fails with old copy - azure-devops-rest-api

I'm trying to leverage the DevOps API to update a new release definition. Ultimately, I will be adding a new environment to the release definition but for now I'm just trying to get the update (PUT) method to work.
I've referenced post this post for information.
The code below gets an existing release definition (id=15), bumps the revision, removes the lastRelease property and then makes a change to the description just to change something.
function getreleasedefinitionrequest($definitionid, $org, $project)
{
$requestpath = "/_apis/release/definitions/" + $definitionid + "?api-version=6.0-preview.4"
$tokeninfo = az account get-access-token | convertfrom-json
$token = $tokeninfo.accessToken
$uribase = "https://vsrm.dev.azure.com/" + $org + "/" + $project
$uri = $uribase+$requestpath
$authheader = "Authorization=Bearer " + $token
$result = az rest --uri $uri --headers $authheader | convertfrom-json
return $result
}
function putreleasedefinitionrequest($bodyfile, $org, $project)
{
$requestpath = "/_apis/release/definitions?api-version=6.0-preview.4"
$tokeninfo = az account get-access-token | convertfrom-json
$token = $tokeninfo.accessToken
$uribase = "https://vsrm.dev.azure.com/" + $org + "/" + $project
$uri = $uribase+$requestpath
$authheader = "Authorization=Bearer " + $token
$result = az rest --method put --uri $uri --headers "Content-Type=application/json" $authheader --body #$bodyfile | convertfrom-json
return $result
}
$definition15 = getreleasedefinitionrequest "15" {org} {project} | select -Last 1
#bump the revision and delete the lastRelease property
$rev = [int] $definition15.revision
$rev++
$definition15.revision = $rev
$definition15.PSObject.properties.remove('lastRelease')
$definition15.description = "make up a change to the description"
$bodyfile = ".\body.json"
$body = $definition15 | convertto-json -Depth 100 | Set-Content -Path $bodyfile
#upate release definition
$putresult = putreleasedefinitionrequest $bodyfile {org} {project} | select -Last 1
The az rest --method put throws an error code complaining that the release is an old copy. I pulled the request from the same version of the API and made changes as describe above. So I'm thinking this new revision is a fresh, new version of the pipeline.
az : Bad Request({"$id":"1","innerException":null,"message":"You are
using an old copy of the release pipeline. Refresh your copy and try
again.","typeName":"Microsoft.VisualStudio.Services.ReleaseManagement.Data.Exceptions.InvalidRequestException,
Microsoft.VisualStudio.Services.ReleaseManagement2.Data","typeKey":"InvalidRequestException","errorCode":0,"eventId":3000})
Are there other changes that are required to successfully make the update?

Remove the $rev++, we don't and we shouldn't change the value of revision manually.
Note: If you read the post carefully, you would see I set the revision number to be the number it is currently on and it works now. So we actually doesn't need to change it, the error You are using an old copy of the release pipeline always results from the change of revision.

Related

How to list out the work items under a team in azure devops

I was successfully able to fetch the workitems under a project using the below http request.
http://dev.azure.com/{organization}/{project}/_apis/wit/reporting/workitemrevisions?includeLatestOnly=true&api-version=5.0-preview.2
Similarly, i need to fetch the team workitems, this is the request i had used.
http://dev.azure.com/{organization}/{project}/{team}/_apis/wit/reporting/workitemrevisions?includeLatestOnly=true&api-version=5.0-preview.2
But, i am not getting the workitems.
Can anyone help me with the syntax.
You can get the work items on a team's Taskboard with:
GET https://dev.azure.com/{organization}/{project}/{team}/_apis/work/taskboardworkitems/{iterationId}?api-version=6.0-preview.1
and on a team Iteration with:
GET https://dev.azure.com/{organization}/{project}/{team}/_apis/work/teamsettings/iterations/{iterationId}/workitems?api-version=6.0-preview.1
You could also do a WIQL query:
$AreaPath = "{Project}\{PathYou'reInterestedIn}"
# Example WIQL gets all User Stories and Bugs that are not removed
$wiql = #"
SELECT
[System.Id]
FROM workitems
WHERE
[System.TeamProject] = '$($AreaPath.Split("\")[0])'
AND [System.AreaPath] UNDER '$($AreaPath.Replace("\", "\\"))'
AND [System.State] <> 'Removed'
AND [System.WorkItemType] IN ('User Story', 'Bug')
ORDER BY [System.ChangedDate] DESC
"#
$uri = "https://dev.azure.com/{org}/_apis/wit/wiql?api-version=6.1-preview.2"
# add your access token to the headers
$headers = #{ Authorization = "Basic " + + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$YourAccessTokenValue"))
# I wrote the WIQL to be readable - this makes it suitable for a JSON request
$query = #{ query = ($wiql.split([System.Environment]::NewLine) | `
ForEach-Object { "$($_.Trim())" }) -join " " } | `
ConvertTo-Json
$workItemList = (Invoke-RestMethod -Uri $uri -Headers $headers -Method Post -Body $query).workItems

How to get only the latest version of a package via AzureDevOps REST API

Below API returns all the versions of a specific package. In order to get the latest version of that package, I can use the isLatest:true data from the returned response.
https://learn.microsoft.com/en-us/rest/api/azure/devops/artifacts/artifact%20%20details/get%20package%20versions?view=azure-devops-rest-6.0
But I was wondering if there is way to only get the latest version in the response rather than all the version? Is it possible?
If there is no possibility of that, then a second question - Will latest version be always the first item in the response? (I assume there is a limit to the return item count (1000?) so I was wondering if one API call would always be sufficient if I need to get the latest version.
If there is no possibility of that, then a second question - Will latest version be always the first item in the response?
The answer is yes. The latest version be always the first item in the response.
As test, I published a test package to my feed with versions 1.0.0, 2.0.0, then published the version 1.0.1-preview1.2, 1.0.1. But Azure devops will be sorted in order of package version:
So, we could use REST API with powershell parameter Select-Object -first 1 to get the latest package version:
$connectionToken="Your PAT"
$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($connectionToken)"))
$url = 'GET https://feeds.dev.azure.com/{organization}/{project}/_apis/packaging/Feeds/{feedId}/Packages/{packageId}/versions?api-version=6.0-preview.1
'
$PackageInfo = (Invoke-RestMethod -Uri $url -Method Get -UseDefaultCredential -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)})
$LatestVersion= $PackageInfo.value.version | Select-Object -first 1
Write-Host "Latest package Version = $LatestVersion"
The test result:
For the first question, I am afraid that there is no out-of-the-box parameter to return the latest version of a package directly through the rest api, because the isLatest parameter is not provided in rest api url to return the latest version. In addition, the $top parameter is not provided in the url parameteres to specify the returned count.
Will latest version be always the first item in the response?
For the second question, the answer is Yes, the latest version will be always the first item in the response.
So as workaround, we can simply filter response through powershell script to return the latest package version.
$url = 'https://feeds.dev.azure.com/{org}/_apis/packaging/Feeds/{feedId}/Packages/{packageId}/versions?api-version=6.0-preview.1';
$token = "{PAT}"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($token)"))
$response = Invoke-RestMethod -Uri $url -Headers #{Authorization = "Basic $token"} -Method Get
$result = $response.value | Where {$_.isLatest -eq "true"} #|
Write-Host "results = $($result | ConvertTo-Json -Depth 100)"

TFS 2017 Change Release summary description during build

We're using Team Foundation Server 2017. After lots of releasedefinition-making, I'm stucking on a problem.
During the release, I receive a message which I would write directly into the release description summary. I checked for an such an activity in the Marketstore, but I haven't found one.
Am I not able to search for the right activity or is there another way for updating this?
Currently, you can only update release name with Logging command, which requires agent version 2.132+. So the only way to update the release description is adding a powershell script in your release definition. The script is as below:
param (
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $token
)
## Construct a basic auth head using PAT
function BasicAuthHeader()
{
param([string]$authtoken)
$ba = (":{0}" -f $authtoken)
$ba = [System.Text.Encoding]::UTF8.GetBytes($ba)
$ba = [System.Convert]::ToBase64String($ba)
$h = #{Authorization=("Basic{0}" -f $ba);ContentType="application/json"}
return $h
}
$getReleaseUri = "http://TFS2017:8080/tfs/DefaultCollection/TeamProject/_apis/release/releases/$($env:RELEASE_RELEASEID)?api-version=3.2-preview"
$h = BasicAuthHeader $token
$release = Invoke-RestMethod -Uri $getReleaseUri -Headers $h -Method Get
# Update an existing variable named d1 to its new value d5
$release.description = "this is a test";
####****************** update the modified object **************************
$release2 = $release | ConvertTo-Json -Depth 100
$release2 = [Text.Encoding]::UTF8.GetBytes($release2)
$updateReleaseUri = "http://TFS2017:8080/tfs/DefaultCollection/TeamProject/_apis/release/releases/$($env:Release_ReleaseId)?api-version=3.2-preview"
$content2 = Invoke-RestMethod -Uri $updateReleaseUri -Method Put -Headers $h -ContentType “application/json” -Body $release2 -Verbose -Debug
write-host "=========================================================="
And with argument -token {PAT}. Check my screenshot below:
I've tested on my side, it can update release description successfully.

How to tell if TFS vNext build failed via variable?

I am trying to perform different actions (such as copying to a different directory on publish) if a build failed or not. I am unable to find any documentation about any variables that let me know if it failed or not. Can anyone let me know how to tell if the build failed or not?
You can create a PowerShell script to call Rest API to get the build information (You need to enable the alternative credential):
[String]$buildID = "$env:BUILD_BUILDID"
[String]$project = "$env:SYSTEM_TEAMPROJECT"
[String]$projecturi = "$env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"
$username="alternativeusername"
$password="alternativepassword"
$basicAuth= ("{0}:{1}"-f $username,$password)
$basicAuth=[System.Text.Encoding]::UTF8.GetBytes($basicAuth)
$basicAuth=[System.Convert]::ToBase64String($basicAuth)
$headers= #{Authorization=("Basic {0}"-f $basicAuth)}
$url= $projecturi + $project + "/_apis/build/builds/" + $buildID + "/timeline?api-version=2.0"
Write-Host $url
$responseBuild = Invoke-RestMethod -Uri $url -headers $headers -Method Get | select records
And then you can check the result in the information to see if there is any failed steps and then perform the actions you want:
foreach ($record in $responseBuild.records)
{
$result = $record.result
##xxxxxxxxxxxxxxxxxxxx
}

update Jenkins credentials by script

I have a Jenkins server running on Windows. It stores a username:password in the credentials plugin. This is a service user that gets its password updated regularly.
I'm looking for a way to run a script, preferably Powershell, that will update that credential in the Jenkins password store so that it's always up to date when I use it in a build job script.
The password is managed by a Thycotic Secret Server install so I should be able to automate the process of keeping this password up to date, but I have found almost no leads for how to accomplish this, even though the blog post by the guy who wrote the credentials api mentions almost exactly this scenario and then proceeds to just link to the credentials plugin's download page that says nothing about how to actually use the api.
Update
The accepted answer works perfectly, but the rest method call example is using curl, which if you're using windows doesn't help much. Especially if you are trying to invoke the REST URL but your Jenkins server is using AD Integration. To achieve this you can use the following script.
Find the userId and API Token by going to People > User > configure > Show API Token.
$user = "UserID"
$pass = "APIToken"
$pair = "${user}:${pass}"
$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
$base64 = [System.Convert]::ToBase64String($bytes)
$basicAuthValue = "Basic $base64"
$headers = #{ Authorization = $basicAuthValue }
Invoke-WebRequest `
-uri "http://YourJenkinsServer:8080/scriptler/run/changeCredentialPassword.groovy?username=UrlEncodedTargetusername&password=URLEncodedNewPassword" `
-method Get `
-Headers $headers
Jenkins supports scripting with the Groovy language. You can get a scripting console by opening in a browser the URL /script of your Jenkins instance. (i.e: http://localhost:8080/script)
The advantage of the Groovy language (over powershell, or anything else) is that those Groovy scripts are executed within Jenkins and have access to everything (config, plugins, jobs, etc).
Then the following code would change the password for user 'BillHurt' to 's3crEt!':
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl
def changePassword = { username, new_password ->
def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class,
Jenkins.instance
)
def c = creds.findResult { it.username == username ? it : null }
if ( c ) {
println "found credential ${c.id} for username ${c.username}"
def credentials_store = Jenkins.instance.getExtensionList(
'com.cloudbees.plugins.credentials.SystemCredentialsProvider'
)[0].getStore()
def result = credentials_store.updateCredentials(
com.cloudbees.plugins.credentials.domains.Domain.global(),
c,
new UsernamePasswordCredentialsImpl(c.scope, c.id, c.description, c.username, new_password)
)
if (result) {
println "password changed for ${username}"
} else {
println "failed to change password for ${username}"
}
} else {
println "could not find credential for ${username}"
}
}
changePassword('BillHurt', 's3crEt!')
Classic automation (/scriptText)
To automate the execution of this script, you can save it to a file (let's say /tmp/changepassword.groovy) and run the following curl command:
curl -d "script=$(cat /tmp/changepassword.groovy)" http://localhost:8080/scriptText
which should respond with a HTTP 200 status and text:
found credential 801cf176-3455-4b6d-a461-457a288fd202 for username BillHurt
password changed for BillHurt
Automation with the Scriptler plugin
You can also install the Jenkins Scriptler plugin and proceed as follow:
Open the Scriptler tool in side menu
fill up the 3 first field taking care to set the Id field to changeCredentialPassword.groovy
check the Define script parameters checkbox
add 2 parameters: username and password
paste the following script:
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl
def changePassword = { username, new_password ->
def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class,
jenkins.model.Jenkins.instance
)
def c = creds.findResult { it.username == username ? it : null }
if ( c ) {
println "found credential ${c.id} for username ${c.username}"
def credentials_store = jenkins.model.Jenkins.instance.getExtensionList(
'com.cloudbees.plugins.credentials.SystemCredentialsProvider'
)[0].getStore()
def result = credentials_store.updateCredentials(
com.cloudbees.plugins.credentials.domains.Domain.global(),
c,
new UsernamePasswordCredentialsImpl(c.scope, null, c.description, c.username, new_password)
)
if (result) {
println "password changed for ${username}"
} else {
println "failed to change password for ${username}"
}
} else {
println "could not find credential for ${username}"
}
}
changePassword("$username", "$password")
and click the Submit button
Now you can call the following URL to change the password (replacing the username and password parameter): http://localhost:8080/scriptler/run/changeCredentialPassword.groovy?username=BillHurt&password=s3crEt%21 (notice the need to urlencode the parameters' value)
or with curl:
curl -G http://localhost:8080/scriptler/run/changeCredentialPassword.groovy --data-urlencode 'username=BillHurt' --data-urlencode "password=s3crEt!"
sources:
Printing a list of credentials and their IDs
Create UserPrivateKeySource Credential via Groovy?
credential plugin source code
Scriptler plugin
Search engine tip: use keywords 'Jenkins.instance.', 'com.cloudbees.plugins.credentials' and UsernamePasswordCredentialsImpl
Decided to write a new answer although it is basically some update to #Tomasleveil's answer:
removing deprecated calls (thanks to jenkins wiki, for other listing options see plugin consumer guide)
adding some comments
preserve credentials ID to avoid breaking existing jobs
lookup credentials by description because usernames are rarely so unique (reader can easily change this to ID lookup)
Here it goes:
credentialsDescription = "my credentials description"
newPassword = "hello"
// list credentials
def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
// com.cloudbees.plugins.credentials.common.StandardUsernameCredentials to catch all types
com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials.class,
Jenkins.instance,
null,
null
);
// select based on description (based on ID might be even better)
cred = creds.find { it.description == credentialsDescription}
println "current values: ${cred.username}:${cred.password} / ${cred.id}"
// not sure what the other stores would be useful for, but you can list more stores by
// com.cloudbees.plugins.credentials.CredentialsProvider.all()
credentials_store = jenkins.model.Jenkins.instance.getExtensionList(
'com.cloudbees.plugins.credentials.SystemCredentialsProvider'
)[0].getStore()
// replace existing credentials with a new instance
updated = credentials_store.updateCredentials(
com.cloudbees.plugins.credentials.domains.Domain.global(),
cred,
// make sure you create an instance from the correct type
new com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl(cred.scope, cred.id, cred.description, cred.username, newPassword)
)
if (updated) {
println "password changed for '${cred.description}'"
} else {
println "failed to change password for '${cred.description}'"
}
I never found a way to get the Groovy script to stop updating the credential ID, but I noticed that if I used the web interface to update credentials, the ID did not change.
With that in mind the script below will in effect script the Jenkins web interface to do the updates.
Just for clarification, the reason this is important is because if you are using something like the Credentials binding plugin to use credentials in your job, updating the ID to the credential will break that link and your job will fail. Please excuse the use of $args rather than param() as this is just an ugly hack that I will refine later.
Note the addition of the json payload to the form fields. I found out the hard way that this is required in this very specific format or the form submission will fail.
$username = $args[0]
$password = $args[1]
Add-Type -AssemblyName System.Web
#1. Log in and capture the session.
$homepageResponse = Invoke-WebRequest -uri http://Servername.domain.com/login?from=%2F -SessionVariable session
$loginForm = $homepageResponse.Forms['login']
$loginForm.Fields.j_username = $username
$loginForm.Fields.j_password = $password
$loginResponse = Invoke-WebRequest `
-Uri http://Servername.domain.com/j_acegi_security_check `
-Method Post `
-Body $loginForm.Fields `
-WebSession $session
#2. Get Credential ID
$uri = "http://Servername.domain.com/credential-store/domain/_/api/xml"
foreach($id in [string]((([xml](Invoke-WebRequest -uri $uri -method Get -Headers $headers -WebSession $session).content)).domainWrapper.Credentials | Get-Member -MemberType Property).Name -split ' '){
$id = $id -replace '_',''
$uri = "http://Servername.domain.com/credential-store/domain/_/credential/$id/api/xml"
$displayName = ([xml](Invoke-WebRequest -uri $uri -method Get -Headers $headers -WebSession $session).content).credentialsWrapper.displayName
if($displayName -match $username){
$credentialID = $id
}
}
#3. Get Update Form
$updatePage = Invoke-WebRequest -Uri "http://Servername.domain.com/credential-store/domain/_/credential/$credentialID/update" -WebSession $session
$updateForm = $updatePage.Forms['update']
$updateForm.Fields.'_.password' = $password
#4. Submit Update Form
$json = #{"stapler-class" = "com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl";
"scope"="GLOBAL";"username"="domain\$username";"password"=$password;"description"="";"id"=$id} | ConvertTo-Json
$updateForm.Fields.Add("json",$json)
Invoke-WebRequest `
-Uri "http://Servername.domain.com/credential-store/domain/_/credential/$credentialID/updateSubmit" `
-Method Post `
-Body $updateForm.Fields `
-WebSession $session

Resources