In my previous post, I shared a script that used the Octopus API to create a defect, with the aim of it being added to a TeamCity build and chaining the build to a deploy/test build step in TeamCity: the aim being to raise an Octopus defect if a test fails whilst the deployment to the environment succeeded. You can read more about it here.

What makes this a challenge is that there is no way to have a chained build that runs if, and only if, a build has failed. So as with Octopus you have to use the TeamCity API. In this script I get the the status of the last build that deployed/ran the tests, and if this build succeeded I do nothing. So yes this chained build has to always run post deploy/test phase.

Where it gets interesting though is if the build failed. Here we raise a defect, but not before checking to see if there are any defects raised that are still unresolved, as only one Octopus defect can be unresolved at any one time.

At no point am I putting a “resolve defect” step here: we want to fail forward every-time, and we want the fix to come from the developers checking in code and fixing it that way. This will raise a new build, create a new release etc, rendering the defective environment forever in this state.


$buildType = "Av2014_DbDeploytoDev"
$epicFail = "Failure"
$root = "http://localhost:84/httpAuth/app/rest/builds?locator=buildType:"+$buildType
$user = "SQLTraining"
$pass= "SQLTraining123"
$secpasswd = ConvertTo-SecureString $pass -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($user, $secpasswd)

$result = Invoke-WebRequest $root -Credential $credential -UseBasicParsing
[xml]$xml = $result.Content
[string]$status= $xml.builds.build | select -first 1 | Format-Table -AutoSize -Property status -HideTableHeaders | Out-String
$status = $status.Trim()
if ($status -eq $epicFail)
{
write-host "Build Failed. Raising a defect."
#This script create a defect of the latest release.
$api = "http://localhost:83"
$OctopusAPIKey = "API-WRWYWXVOPUFBH8YLD7KXZLTQJXS" #key from octopus
$ProjectName = "AV2014" #project name
$ReleaseVersion = "1.0.14" #build number from TeamCity
$header = @{ "X-Octopus-ApiKey" = $OctopusAPIKey } #header for web requests

$something = @{ description = 'yet another test defect' } | ConvertTo-Json #description of defect is required when posting new defect

#retrieve project id; required for getting release
$projectUri = "$api/api/projects/$ProjectName"
$Project = Invoke-WebRequest -Uri $projectUri -Headers $header -UseBasicParsing | ConvertFrom-Json
$ProjectId = $Project.Id

$relUri = "$api/api/projects/$ProjectId/releases/$ReleaseVersion"
$releaseDetails = Invoke-WebRequest -Uri $relUri -Headers $header -UseBasicParsing | ConvertFrom-Json

$reportDefect = $releaseDetails.Links.ReportDefect #url to report defects
$defectApiUrl = $releaseDetails.Links.Defects #url to get info on defects
$resolveDefect = $releaseDetails.Links.ResolveDefect #url to resolve defects

$getDefectUrl = "$api$defectApiUrl" #make full defect url
$reportDefectUrl = "$api$reportDefect" #make full report url
$resolveDefectUrl = "$api$resolveDefect" #make full resolve url

#check if unresolved defect exists. If it does, then write-error and exit script
$StatusesofDefects= Invoke-WebRequest -Uri $getDefectUrl -Method Get -Headers $Header -UseBasicParsing | ConvertFrom-Json
$StatusesofDefects.Items.Status
foreach ($DefectStatus in $StatusesofDefects.Items.Status)
{
if ($DefectStatus -eq 'Unresolved')
{
Write-Error "An unresolved Octopus defect already exists. Resolve previous Octopus defects before attempting to raise another defect."
exit
}

}

#we'll only get here if there are no unresolved defects
write-host "raising defect for project" $ProjectName "for release version" $ReleaseVersion
Invoke-WebRequest -Uri $reportDefectUrl -Method Post -Headers $Header -Body $something -UseBasicParsing | ConvertFrom-Json

#commented out resolve defect. We don't need it to run, but it's here for future reference.
#Invoke-WebRequest -Uri $resolveDefectUrl -Method Post -Headers $header | ConvertFrom-Json

}
elseif ($status -ne $epicFail) {write-host "Build has not failed. Not raising a defect."}

As with the script from my previous post, there is a call to action - tidy it up; perhaps splitting it up into functions, passing parameters into the script as opposed to having them hard coded like this, because IRL this script wouldn’t work without it. I’ve actually provided a solution to the previous posts call to action in this one, so who knows, maybe I’ll post an updated version of this script one day?

And here below is the script that access just the TeamCity API, as I shared the Octopus API script on it’s own yesterday.


$buildType = "Av2014_DbDeploytoDev"
$epicFail = "Failure"
$root = "http://localhost:84/httpAuth/app/rest/builds?locator=buildType:"+$buildType
$user = "SQLTraining"
$pass= "SQLTraining123"
$secpasswd = ConvertTo-SecureString $pass -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($user, $secpasswd)

$result = Invoke-WebRequest $root -Credential $credential -UseBasicParsing
[xml]$xml = $result.Content
[string]$status= $xml.builds.build | select -first 1 | Format-Table -AutoSize -Property status -HideTableHeaders | Out-String
$status = $status.Trim()
if ($status -eq $epicFail)
{
write-host "Build Failed. Raising a defect."
#This script create a defect of the latest release.
}
elseif ($status -ne $epicFail) {write-host "Build has not failed. Not raising a defect."}

That’s all for today, have a good one. Happy scripting!