Support for prioritizing tasks directly in Octopus is available from 2023.4.
This script can be used to move critical deployments from the bottom of the queue to the top of the queue.
How it works:
- Look at all the pending tasks in the queue. All in-process items are left as is.
- If there are any deployments or runbook runs, it will check to see if they match specified criteria.
- If any matching runbook runs or deployment tasks are found, it will loop through the queue and cancel all the items before them.
- If the script cancels any runbook runs or deployments, it will resubmit them using the same values.
For example, you have this in your pending queue:
- Deployment to Dev
- Runbook run on Maintenance
- Retention policy run
- Deployment to Production
It will cancel the deployment to Dev
, the runbook run on Maintenance
, and the retention policy run. It will then resubmit the deployment to Dev,
and the runbook run on Maintenance
using the same parameters. The user who appears in the audit log will be the one attached to the API key.
Usage
Provide values for:
- Octopus URL (required)
- Octopus API Key (required)
The script looks for tasks one of two ways:
- Task Id List - comma separated list of task ids to move to the top of the queue
- Matching based on criteria, see below
Matching based on criteria
- Space List - comma separated list of spaces to look for (optional)
- Environment List - comma separated list of environments to look for (optional)
- Options:
EnvironmentName
- egProduction
- looks for theProduction
environment in any space from the space list.EnvironmentName::SpaceName
- egProduction::Default
- looks for theProduction
environment in theDefault
space only.
- Options:
- Project List - comma separated list of projects to look for (optional)
- Options:
ProjectName
- egHello World
- looks for theHello World
project in any space from the space list.ProjectName::SpaceName
- egHello World::Default
- looks for theHello World
project in theDefault
space only.
- Options:
- Tenant List - comma separated list of tenants to look for (optional)
- Options:
TenantName
- egMy Tenant
- looks for theMy Tenant
tenant in any space from the space list.TenantName::SpaceName
- egMy Tenant::Default
- looks for theMy Tenant
tenant in theDefault
space only.
- Options:
- Match Type - how the match will happen
- Options:
Or
- will look for runbook run or deployment that matches any of the filters - egProduction
ORHello World
ORMy Tenant
And
- will look for runbook run or deployment that matches all of the filters - egProduction
ANDHello World
ANDMy Tenant
- If a filter isn’t supplied it is excluded from the check
- Options:
- Task Type - what task type to look for
- Options:
Deploy
- looks for deployments onlyRunbookRun
- looks for runbook runs onlyBoth
- looks for both deployments and runbooks runs
- Options:
You must supply at least one task id OR at least one filter for environments OR projects OR tenants must be supplied.
Script
PowerShell (REST API)
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
$octopusUrl = "https://your-octopus-url" ## Octopus URL to look at
$octopusApiKey = "API-YOUR-KEY" ## API key of user who has permissions to view all spaces, cancel tasks, and resubmit runbooks runs and deployments
$spaceList = "Default" ## Comma separated list of spaces to monitor
$environmentList = "Production" ## Comma separated list of environments to look for (can be blank)
$projectList = "" ## Comma separated list of projects to look for (can be blank)
$tenantList = "" ## Comma separated list of tenants to look for (can be blank)
$matchType = "Or" ## How you want to match, OR = Task matches Environment OR Project OR Tenant; AND = task matches Environment AND Project AND Tenant (when supplied)
$taskType = "Both" ## Look for runbook runs, or deployments or Both. Options are Both, Deploy, RunbookRun
$taskIdList = "" ## Comma separated list of task ids to move to the top of the queue
$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-FilteredOctopusItem
{
param(
$itemList,
$itemName
)
if ($itemList.Items.Count -eq 0)
{
Write-OctopusCritical "Unable to find $itemName. Exiting with an exit code of 1."
return $null
}
$item = $itemList.Items | Where-Object { $_.Name -eq $itemName}
if ($null -eq $item)
{
Write-OctopusCritical "Unable to find $itemName. Exiting with an exit code of 1."
return $null
}
return $item
}
function Get-OctopusItemByName
{
param(
$itemName,
$itemType,
$endpoint,
$spaceId,
$octopusUrl,
$octopusApiKey
)
if ([string]::IsNullOrWhiteSpace($itemName))
{
return $null
}
Write-OctopusInformation "Attempting to find $itemType with the name of $itemName"
$itemList = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint "$($endPoint)?partialName=$([uri]::EscapeDataString($itemName))&skip=0&take=100" -spaceId $spaceId -apiKey $octopusApiKey -method "GET"
$item = Get-FilteredOctopusItem -itemList $itemList -itemName $itemName
if ($null -eq $item)
{
Write-OctopusInformation "Unable to find $itemType $itemName"
return $null
}
Write-OctopusInformation "Successfully found $itemType $itemName with an id of $($item.Id)"
return $item
}
function Get-SplitItemIntoArray
{
param (
$itemToSplit
)
if ($itemToSplit -like "*`n*")
{
return @(($itemToSplit -Split "`n").Trim())
}
if ($itemToSplit -like "*,*")
{
return @(($itemToSplit -Split ",").Trim())
}
return @($itemToSplit)
}
function Get-OctopusSpaceList
{
param(
$spaceList,
$octopusUrl,
$octopusApiKey
)
if ([string]::IsNullOrWhiteSpace($spaceList))
{
$rawOctopusSpaceList = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint "spaces?skip=0&take=10000" -spaceId $null -apiKey $octopusApiKey -method "GET"
return $rawOctopusSpaceList.Items
}
$spaceListSplit = @(Get-SplitItemIntoArray -itemToSplit $spaceList)
$returnList = @()
foreach ($spaceName in $spaceListSplit)
{
if ([string]::IsNullOrWhiteSpace($spaceName) -eq $false)
{
$octopusSpace = Get-OctopusItemByName -itemName $spaceName -itemType "Space" -endpoint "spaces" -spaceId $null -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey
if ($null -ne $octopusSpace)
{
$returnList += $octopusSpace
}
}
}
return $returnList
}
function Get-OctopusItemList
{
param(
$octopusSpaceList,
$itemList,
$itemType,
$endpoint,
$octopusApiKey,
$octopusUrl
)
if ([string]::IsNullOrWhiteSpace($itemList))
{
Write-Host "The list for $itemType was empty"
return @()
}
$itemListSplit = @(Get-SplitItemIntoArray -itemToSplit $itemList)
$returnList = @()
foreach ($itemName in $itemListSplit)
{
$splitItem = $itemName -split "::"
if ($splitItem.Count -gt 1 -and [string]::IsNullOrWhiteSpace($splitItem[1]) -eq $false)
{
Write-OctopusInformation "The item $itemName included a space name, only pulling back the information for that space"
$spaceId = $octopusSpaceList | Where-Object { $_.Name.ToLower().Trim() -eq $splitItem[1].ToLower().Trim() }
if ($null -eq $spaceId)
{
Write-OctopusInformation "The space name $($splitItem[1]) was not included in the space filter. Skipping this option."
continue
}
$octopusItem = Get-OctopusItemByName -itemName $splitItem[0] -itemType $itemType -endpoint $endpoint -spaceId $spaceId -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey
if ($null -ne $octopusItem)
{
$returnList += $octopusItem
}
continue
}
foreach ($space in $octopusSpaceList)
{
$octopusItem = Get-OctopusItemByName -itemName $itemName -itemType $itemType -endpoint $endpoint -spaceId $space.Id -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey
if ($null -ne $octopusItem)
{
$returnList += $octopusItem
}
}
}
return $returnList
}
function Get-QueuedOctopusTasks
{
param (
$octopusApiKey,
$octopusUrl
)
$queuedTasks = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint "Tasks?states=Queued&skip=0&take=1000" -spaceId $null -apiKey $octopusApiKey -method "GET" -ignoreCache $true
$returnList = @()
$currentTime = $(Get-Date).ToUniversalTime()
Write-OctopusInformation "Looping through the found items in reverse order because the Queue is FIFO but the return object is ordered by date DESC"
for($i = $queuedTasks.Items.Count - 1; $i -ge 0; $i--)
{
$task = $queuedTasks.Items[$i]
if ($null -ne $task.QueueTime)
{
$compareTime = [DateTime]::Parse($task.QueueTime)
$compareTime = $compareTime.ToUniversalTime()
Write-OctopusVerbose "Checking to see if $compareTime is ahead of the $currentTime"
if ($compareTime -gt $currentTime)
{
Write-OctopusInformation "The queued task $($task.Id) has a queue time $($task.QueueTime) in the future. That means this is a scheduled deployment. Skipping this task."
continue
}
}
if ($null -ne $task.StartTime)
{
Write-OctopusInformation "The queued task $($task.Id) has a start time, meaning it was picked up, work was done, then it was added back to the queue. Skipping."
continue
}
if ($true -eq $task.HasPendingInterruptions)
{
Write-OctopusInformation "The task $($task.Id) has pending interruptions, this means the deployment has started and is awaiting someone to respond. Skipping this task."
continue
}
$returnList += $task
}
return $returnList
}
function Test-OctopusListHasId
{
param (
$octopusList,
$octopusId
)
$findItem = $octopusList | Where-Object { $_.Id -eq $octopusId }
if ($null -eq $findItem)
{
return $false
}
return $true
}
function Get-RunbookRunDetailsFromTask
{
param (
$runbookTask,
$octopusUrl,
$octopusApiKey
)
return Invoke-OctopusApi -endPoint "runbookRuns/$($runbookTask.Arguments.RunbookRunId)" -octopusUrl $octopusUrl -spaceId $runbookTask.SpaceId -apiKey $octopusApiKey -method "GET"
}
function Get-DeploymentDetailsFromTask
{
param (
$deploymentTask,
$octopusUrl,
$octopusApiKey
)
return Invoke-OctopusApi -endPoint "deployments/$($deploymentTask.Arguments.DeploymentId)" -octopusUrl $octopusUrl -spaceId $deploymentTask.SpaceId -apiKey $octopusApiKey -method "GET"
}
Write-OctopusInformation "Current Task Id: $currentTaskId"
Write-OctopusInformation "Space List: $spaceList"
Write-OctopusInformation "Environment List: $environmentList"
Write-OctopusInformation "Project List: $projectList"
Write-OctopusInformation "Tenant List: $tenantList"
Write-OctopusInformation "Octopus URL: $octopusUrl"
Write-OctopusInformation "Match Type: $matchType"
Write-OctopusInformation "Task Id List: $taskIdList"
$queuedTasks = @(Get-QueuedOctopusTasks -octopusApiKey $octopusApiKey -octopusUrl $octopusUrl)
if ($queuedTasks.Length -eq 0)
{
Write-OctopusSuccess "No queued tasks found that can block a deployment. Exiting."
exit 1
}
$octopusInformation = @{
TaskIdList = @(Get-SplitItemIntoArray -itemToSplit $taskIdList)
}
if ([string]::IsNullOrWhiteSpace($taskIdList))
{
$octopusInformation.SpaceList = @(Get-OctopusSpaceList -spaceList $spaceList -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey)
$octopusInformation.EnvironmentList = @(Get-OctopusItemList -octopusSpaceList $octopusInformation.SpaceList -itemList $environmentList -itemType "Environment" -endpoint "environments" -octopusApiKey $octopusApiKey -octopusUrl $octopusUrl)
$octopusInformation.HasEnvironmentFilter = $octopusInformation.EnvironmentList.Count -gt 0
$octopusInformation.ProjectList = @(Get-OctopusItemList -octopusSpaceList $octopusInformation.SpaceList -itemList $projectList -itemType "Project" -endpoint "projects" -octopusApiKey $octopusApiKey -octopusUrl $octopusUrl)
$octopusInformation.HasProjectFilter = $octopusInformation.ProjectList.Count -gt 0
$octopusInformation.TenantList = @(Get-OctopusItemList -octopusSpaceList $octopusInformation.SpaceList -itemList $tenantList -itemType "Tenant" -endpoint "tenants" -octopusApiKey $octopusApiKey -octopusUrl $octopusUrl)
$octopusInformation.HasTenantFilter = $octopusInformation.TenantList.Count -gt 0
if ($octopusInformation.EnvironmentList.Count -eq 0 -and $octopusInformation.ProjectList.Count -eq 0 -and $octopusInformation.TenantList.Count -eq 0)
{
Write-OctopusCritical "No environments OR projects OR tenants provided to filter on. You must provide at least one environment OR project OR tenant."
exit 1
}
Write-OctopusSuccess "Going to look for any $taskType in the spaces ($(($octopusInformation.SpaceList | Select-Object -ExpandProperty Id) -join ", ")) matching "
Write-OctopusSuccess "Environments ($(($octopusInformation.EnvironmentList | Select-Object -ExpandProperty Id) -join " OR ")) $matchType"
Write-OctopusSuccess "Projects ($(($octopusInformation.ProjectList | Select-Object -ExpandProperty Id) -join " OR ")) $matchType"
Write-OctopusSuccess "Tenants ($(($octopusInformation.TenantList | Select-Object -ExpandProperty Id) -join " OR "))"
}
else
{
Write-OctopusSuccess "Going to look for the tasks ($($octopusInformation.TaskIdList -join ", "))"
}
$matchingTasks = @()
Write-OctopusInformation "Attempting to find any matching tasks based on the filtering criteria."
foreach ($task in $queuedTasks)
{
if ($octopusInformation.TaskIdList -contains $task.Id)
{
Write-OctopusInformation "The task $($task.Id) was found in the list of task ids. Adding to list."
$matchingTasks += $task
continue
}
if ($task.Name -ne "Deploy" -and $task.Name -ne "RunbookRun")
{
Write-Information "The task not a deployment or a runbook run. It is $($task.Description). Moving onto next task."
continue
}
if ($taskType -ne "Both" -and $taskType -ne $task.Name)
{
Write-Information "You have selected to filter on $taskType only and this task is a $($task.Name). Moving onto the next task."
continue
}
if ((Test-OctopusListHasId -octopusList $octopusInformation.SpaceList -octopusId $task.SpaceId) -eq $false)
{
Write-Information "The task is not for any spaces specified. Moving onto the next task."
continue
}
if ($task.Name -eq "RunbookRun")
{
$itemDetails = Get-RunbookRunDetailsFromTask -runbookTask $task -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey
}
else
{
$itemDetails = Get-DeploymentDetailsFromTask -deploymentTask $task -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey
}
$matchesEnvironmentFilter = $octopusInformation.HasEnvironmentFilter -eq $true -and (Test-OctopusListHasId -octopusList $octopusInformation.EnvironmentList -octopusId $itemDetails.EnvironmentId)
Write-OctopusInformation "$($task.Name) $($itemDetails.Id) Matches Environment Filter $matchesEnvironmentFilter"
$matchesProjectFilter = $octopusInformation.HasProjectFilter -eq $true -and (Test-OctopusListHasId -octopusList $octopusInformation.ProjectList -octopusId $itemDetails.ProjectId)
Write-OctopusInformation "$($task.Name) $($itemDetails.Id) Matches Project Filter $matchesProjectFilter"
$matchesTenantFilter = $octopusInformation.HasTenantFilter -eq $true -and $null -ne $itemDetails.TenantId -and (Test-OctopusListHasId -octopusList $octopusInformation.TenantList -octopusId $itemDetails.TenantId)
Write-OctopusInformation "$($task.Name) $($itemDetails.Id) Matches Tenant Filter $matchesTenantFilter"
if ($matchType -eq "Or" -and ($matchesTenantFilter -eq $true -or $matchesProjectFilter -eq $true -or $matchesEnvironmentFilter -eq $true))
{
Write-OctopusInformation "The match type was OR and one of the filters matched, adding this task to the matching list"
$matchingTasks += $task
continue
}
Write-OctopusInformation "The match type is AND, checking to see if the task matches all the filters"
if ($octopusInformation.HasEnvironmentFilter -eq $true -and $matchesEnvironmentFilter -eq $false)
{
Write-OctopusInformation "The environment filter was provided and the environment $($itemDetails.EnvironmentId) didn't match any environments. Moving onto next task."
continue
}
if ($octopusInformation.HasProjectFilter -eq $true -and $matchesProjectFilter -eq $false)
{
Write-OctopusInformation "The project filter was provided and the project $($itemDetails.ProjectId) didn't match any projects. Moving onto next task."
continue
}
if ($octopusInformation.HasTenantFilter -eq $true -and $matchesTenantFilter -eq $false)
{
Write-OctopusInformation "The tenant filter was provided and the tenant $($itemDetails.TenantId) didn't match any tenants. Moving onto next task."
continue
}
$matchingTasks += $task
}
if ($matchingTasks.Count -eq 0)
{
Write-OctopusSuccess "No matching tasks found, exiting."
exit 0
}
Write-OctopusSuccess "Matching tasks found, checking where they are in the queue."
$matchingTaskCounter = 0
Write-OctopusInformation "Looping through all the queued tasks again to find which tasks to cancel."
foreach ($task in $queuedTasks)
{
if ((Test-OctopusListHasId -octopusList $matchingTasks -octopusId $task.Id))
{
$matchingTaskCounter += 1
Write-OctopusInformation "The task $($task.Id) is one we want to move to the top of queue, leaving as is."
if ($matchingTaskCounter -eq $matchingTasks.Count)
{
Write-OctopusInformation "All the matching tasks we want to move to the top of the queue have been found, exiting"
break
}
continue
}
$updatedTask = Invoke-OctopusApi -endPoint "tasks/$($task.Id)" -octopusUrl $octopusUrl -spaceId $null -apiKey $octopusApiKey -method "GET" -ignoreCache $true
if ($updatedTask.HasBeenPickedUpByProcessor -eq $true)
{
Write-OctopusInformation "The task $($task.Id) has already been picked up and started processing, moving on."
continue
}
$canceledTaskResult = Invoke-OctopusApi -endPoint "tasks/$($task.Id)/cancel" -octopusUrl $octopusUrl -spaceId $null -apiKey $octopusApiKey -method "POST" -ignoreCache $true
Write-OctopusSuccess "Task $($task.Description) has been successfully cancelled"
if ($task.Name -eq "Deploy")
{
Write-OctopusInformation "Task $($task.Id) is a deployment, setting up a redeploy."
$deploymentInfo = Get-DeploymentDetailsFromTask -deploymentTask $task -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey
$bodyRaw = @{
EnvironmentId = $deploymentInfo.EnvironmentId
ExcludedMachineIds = $deploymentInfo.ExcludedMachineIds
ForcePackageDownload = $deploymentInfo.ForcePackageDownload
ForcePackageRedeployment = $deploymentInfo.ForcePackageRedeployment
FormValues = $deploymentInfo.FormValues
QueueTime = $null
QueueTimeExpiry = $null
ReleaseId = $deploymentInfo.ReleaseId
SkipActions = $deploymentInfo.SkipActions
SpecificMachineIds = $deploymentInfo.SpecificMachineIds
TenantId = $deploymentInfo.TenantId
UseGuidedFailure = $deploymentInfo.UseGuidedFailure
}
$newDeployment = Invoke-OctopusApi -endPoint "deployments" -spaceId $task.SpaceId -octopusUrl $octopusUrl -apiKey $octopusApiKey -method "POST" -item $bodyRaw
Write-OctopusSuccess "$($task.Description) has been successfully resubmitted with the new id $($newDeployment.TaskId)"
}
if ($task.Name -eq "RunbookRun")
{
Write-OctopusInformation "Task $($task.Id) is a runbook run, configuring a re-run."
$runbookInfo = Get-RunbookRunDetailsFromTask -runbookTask $task -octopusUrl $octopusUrl -octopusApiKey $octopusApiKey
$bodyRaw = @{
EnvironmentId = $runbookInfo.EnvironmentId
ExcludedMachineIds = $runbookInfo.ExcludedMachineIds
ForcePackageDownload = $runbookInfo.ForcePackageDownload
ForcePackageRedeployment = $runbookInfo.ForcePackageRedeployment
FormValues = $runbookInfo.FormValues
QueueTime = $null
QueueTimeExpiry = $null
RunbookId = $runbookInfo.RunbookId
SkipActions = $runbookInfo.SkipActions
SpecificMachineIds = $runbookInfo.SpecificMachineIds
TenantId = $runbookInfo.TenantId
UseGuidedFailure = $runbookInfo.UseGuidedFailure
FrozenRunbookProcessId = $runbookInfo.FrozenRunbookProcessId
RunbookSnapshotId = $runbookInfo.RunbookSnapshotId
}
$newDeployment = Invoke-OctopusApi -endPoint "runbookRuns" -spaceId $task.SpaceId -octopusUrl $octopusUrl -apiKey $octopusApiKey -method "POST" -item $bodyRaw
Write-OctopusSuccess "$($task.Description) has been successfully resubmitted with the new id $($newDeployment.TaskId)"
}
}
Help us continuously improve
Please let us know if you have any feedback about this page.
Page updated on Friday, October 20, 2023