Deployments Per Deployment Target Role Report

The Octopus Web Portal allows you to see what deployments have gone out to a specific deployment target, but it doesn’t provide you with a list of deployments for all the deployment targets in a role. This script demonstrates how to generate such a report.

Sample deployments per target role

Please note: The report is generated as a CSV file, formatting was added to the screenshot to make it easier to read.

Usage

Provide values for the following:

  • Octopus URL
  • Octopus API Key
  • Report Path
  • Space Name
  • Target Role
  • Days to Query
PowerShell (REST API)
$octopusUrl = "https://YOURURL"
$octopusApiKey = "YOUR API KEY"
$reportPath = "./Report.csv"
$spaceName = "Default" 
$targetRole = "hello-world"
$daysToQuery = 365

$cachedResults = @{}

function Write-OctopusVerbose
{
    param($message)
    
    Write-Host $message  
}

function Write-OctopusInformation
{
    param($message)
    
    Write-Host $message  
}

function Write-OctopusSuccess
{
    param($message)

    Write-Host $message 
}

function Write-OctopusWarning
{
    param($message)

    Write-Warning "$message" 
}

function Write-OctopusCritical
{
    param ($message)

    Write-Error "$message" 
}

function Invoke-OctopusApi
{
    param
    (
        $octopusUrl,
        $endPoint,
        $spaceId,
        $apiKey,
        $method,
        $item,
        $ignoreCache     
    )

    $octopusUrlToUse = $OctopusUrl
    if ($OctopusUrl.EndsWith("/"))
    {
        $octopusUrlToUse = $OctopusUrl.Substring(0, $OctopusUrl.Length - 1)
    }

    if ([string]::IsNullOrWhiteSpace($SpaceId))
    {
        $url = "$octopusUrlToUse/api/$EndPoint"
    }
    else
    {
        $url = "$octopusUrlToUse/api/$spaceId/$EndPoint"    
    }  

    try
    {        
        if ($null -ne $item)
        {
            $body = $item | ConvertTo-Json -Depth 10
            Write-OctopusVerbose $body

            Write-OctopusInformation "Invoking $method $url"
            return Invoke-RestMethod -Method $method -Uri $url -Headers @{"X-Octopus-ApiKey" = "$ApiKey" } -Body $body -ContentType 'application/json; charset=utf-8' 
        }

        if (($null -eq $ignoreCache -or $ignoreCache -eq $false) -and $method.ToUpper().Trim() -eq "GET")
        {
            Write-OctopusVerbose "Checking to see if $url is already in the cache"
            if ($cachedResults.ContainsKey($url) -eq $true)
            {
                Write-OctopusVerbose "$url is already in the cache, returning the result"
                return $cachedResults[$url]
            }
        }
        else
        {
            Write-OctopusVerbose "Ignoring cache."    
        }

        Write-OctopusVerbose "No data to post or put, calling bog standard invoke-restmethod for $url"
        $result = Invoke-RestMethod -Method $method -Uri $url -Headers @{"X-Octopus-ApiKey" = "$ApiKey" } -ContentType 'application/json; charset=utf-8'

        if ($cachedResults.ContainsKey($url) -eq $true)
        {
            $cachedResults.Remove($url)
        }
        Write-OctopusVerbose "Adding $url to the cache"
        $cachedResults.add($url, $result)

        return $result

               
    }
    catch
    {
        if ($null -ne $_.Exception.Response)
        {
            if ($_.Exception.Response.StatusCode -eq 401)
            {
                Write-OctopusCritical "Unauthorized error returned from $url, please verify API key and try again"
            }
            elseif ($_.Exception.Response.statusCode -eq 403)
            {
                Write-OctopusCritical "Forbidden error returned from $url, please verify API key and try again"
            }
            else
            {                
                Write-OctopusVerbose -Message "Error calling $url $($_.Exception.Message) StatusCode: $($_.Exception.Response.StatusCode )"
            }            
        }
        else
        {
            Write-OctopusVerbose $_.Exception
        }
    }

    Throw "There was an error calling the Octopus API please check the log for more details"
}

function Get-OctopusItemList
{
    param(
        $itemType,
        $endpoint,        
        $spaceId,
        $octopusUrl,
        $octopusApiKey
    )

    if ($null -ne $spaceId) 
    {
        Write-OctopusVerbose "Pulling back all the $itemType in $spaceId"
    }
    else
    {
        Write-OctopusVerbose "Pulling back all the $itemType for the entire instance"
    }
    
    if ($endPoint -match "\?+")
    {
        $endpointWithParams = "$($endPoint)&skip=0&take=10000"
    }
    else
    {
        $endpointWithParams = "$($endPoint)?skip=0&take=10000"
    }

    $itemList = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint $endpointWithParams -spaceId $spaceId -apiKey $octopusApiKey -method "GET"
    
    if ($itemList -is [array])
    {
        Write-OctopusVerbose "Found $($itemList.Length) $itemType."

        return ,$itemList        
    }
    else
    {
        Write-OctopusVerbose "Found $($itemList.Items.Length) $itemType."

        return ,$itemList.Items
    }
}

function Get-OctopusItemByName
{
    param (
        $itemType,
        $itemName,
        $endPoint,
        $spaceId,
        $octopusUrl,
        $octopusApiKey
    )

    $itemList = Get-OctopusItemList -endpoint "$($endpoint)?partialName=$([uri]::EscapeDataString($itemName))" -itemType $itemType -spaceId $spaceId -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey

    $filteredItem = $itemList | Where-Object { $_.Name.ToLower().Trim() -eq $itemName.ToLower().Trim() }

    if ($null -eq $filteredItem)
    {
        Write-OctopusInformation "Unable to find the $itemType $itemName"
        exit 1
    }
    
    return $filteredItem
}

function Test-OctopusObjectHasProperty
{
    param(
        $objectToTest,
        $propertyName
    )

    $hasProperty = Get-Member -InputObject $objectToTest -Name $propertyName -MemberType Properties

    if ($hasProperty)
    {
        Write-OctopusVerbose "$propertyName property found."
        return $true
    }
    else
    {
        Write-OctopusVerbose "$propertyName property missing."
        return $false
    }    
}

$space = Get-OctopusItemByName -itemType "Space" -endPoint "spaces" -itemName $spaceName -spaceId $null -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey
$environmentList = Get-OctopusItemList -itemType "Environments" -endpoint "environments" -spaceId $($space.Id) -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey
$projectList = Get-OctopusItemList -itemType "Projects" -endpoint "projects" -spaceId $($space.Id) -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey
$deploymentTargetList = Get-OctopusItemList -itemType "DeploymentTargets" -endpoint "machines?roles=$($targetRole)" -spaceId $($space.Id) -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey

$deploymentTargetsDeployments = @()
$minDate = Get-Date
$minDate = $minDate.AddDays(($daysToQuery * -1))

Write-Host "The minimum date allowed is: $minDate"

foreach ($deploymentTarget in $deploymentTargetList)
{
    $taskList = Get-OctopusItemList -itemType "Deployment Target Deployments" -endpoint "machines/$($deploymentTarget.Id)/tasks" -spaceId $($space.Id) -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey

    foreach ($task in $taskList)
    {
        if ($task.QueueTime -lt $minDate)
        {            
            break
        }

        if ((Test-OctopusObjectHasProperty -propertyName "DeploymentId" -objectToTest $task.Arguments) -eq $false)
        {
            continue
        }

        $deployment = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint "deployments/$($task.Arguments.DeploymentId)" -spaceId $spaceId -apiKey $octopusApiKey -method "GET" 

        $environment = $environmentList | Where-Object { $_.Id -eq $deployment.EnvironmentId }
        $project = $projectList | Where-Object { $_.Id -eq $deployment.ProjectId }

        $release = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint "releases/$($deployment.ReleaseId)" -spaceId $spaceId -apiKey $octopusApiKey -method "GET" 

        $deploymentTargetsDeployments += @{
            DeploymentTargetName = $deploymentTarget.Name
            DeploymentTargetId = $deploymentTarget.Id
            DeploymentState = $task.State
            Environment = $environment.Name
            Project = $project.Name
            ReleaseVersion = $release.Version
            QueuedTime = $task.QueueTime
        }
    }
}

if (Test-Path $reportPath)
{
    Remove-Item $reportPath
}

New-Item $reportPath -ItemType File

Add-Content -Path $reportPath -Value "Machine Name,Environment Name,Project Name,Release Version,Deployment State,Queue DateTime,Machine Id"

Foreach ($deployedToMachine in $deploymentTargetsDeployments)
{
    Add-Content -Path $reportPath -Value "$($deployedToMachine.DeploymentTargetName),$($deployedToMachine.Environment),$($deployedToMachine.Project),$($deployedToMachine.ReleaseVersion),$($deployedToMachine.DeploymentState),$($deployedToMachine.QueuedTime),$($deployedToMachine.DeploymentTargetId)"
}

Help us continuously improve

Please let us know if you have any feedback about this page.

Send feedback

Page updated on Sunday, January 1, 2023