Run a runbook with prompted variables

This script demonstrates how to programmatically run a runbook when the runbook has prompted variables. It will also wait for the runbook run to complete.

Usage

Provide values for the following:

  • Runbook Base URL
  • Runbook API Key
  • Name of the space
  • Name of the project
  • Name of the runbook
  • Name of the environment
  • Wait for finish
  • Use guided failure mode
  • Use a published snapshot only
  • Cancel in seconds
  • Prompted variables

Prompted variable format

In the PowerShell script the prompted variables should be provided in the format Name::Value with a new line separating them:

PromptedVariableName::My Super Awesome Value
OtherPromptedVariable::Other Super Awesome Value

Script

PowerShell (REST API)
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12

$runbookBaseUrl = "" ## The base url, IE https://samples.octopus.app
$runbookApiKey = "" ## The API KEY
$runbookSpaceName = "Default" ## The name of the space the runbook is located in
$runbookProjectName = "Sample Project" ## the name of the project the runbook is located in
$runbookRunName = "Sample Name" ## the name of the runbook
$runbookEnvironmentName = "" ## The environment name to run the runbook in
$runbookTenantName = "" ## Optional - the name of the tenant to run the runbook for
$runbookWaitForFinish = $true ## set to either $true or $false
$runbookUseGuidedFailure = $false ## set to either $true or $false
$runbookUsePublishedSnapshot = $true ## set to either $true or $false
$runbookCancelInSeconds = 1800 ## 1800 seconds is 30 minutes

$runbookPromptedVariables = "" ## format is "VariableName::VariableValue"

function FindMatchingItemByName 
{		    
	param (
      [string] $EndPoint, 
      [string] $NameToLookFor, 
      [string] $ItemType, 
      [string] $APIKey, 
      [string] $PullFirstItem
    )
    
	$fullUrl = "$($EndPoint)?partialName=$NameToLookFor&skip=0&take=10000"   
    Write-Host "Attempting to find $ItemType $NameToLookFor by hitting $fullUrl"
        
    $header = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
	$header.Add("X-Octopus-ApiKey", $APIKey)
    
    $itemList = Invoke-RestMethod $fullUrl -Headers $header
    $foundItem = $null

    foreach ($item in $itemList.Items)
    {
        if ($item.Name -eq $NameToLookFor -or $PullFirstItem)
        {
            Write-Host "$ItemType matching $NameToLookFor found"
            $foundItem = $item
            break
        }
    }

    if ($foundItem -eq $null)
    {
        Write-Host "$ItemType $NameToLookFor not found, exiting with error"
        exit 1
    }
    
    return $foundItem
}

Write-Host "Runbook Name $runbookRunName"
Write-Host "Runbook Base Url: $runbookBaseUrl"
Write-Host "Runbook Space Name: $runbookSpaceName"
Write-Host "Runbook Project Name: $runbookProjectName"
Write-Host "Runbook Environment Name: $runbookEnvironmentName"
Write-Host "Runbook Tenant Name: $runbookTenantName"
Write-Host "Wait for Finish: $runbookWaitForFinish"
Write-Host "Use Guided Failure: $runbookUseGuidedFailure"
Write-Host "Cancel run in seconds: $runbookCancelInSeconds"
Write-Host "Prompted Variables: $runbookPromptedVariables"
 
$header = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$header.Add("X-Octopus-ApiKey", $runbookApiKey)

$spaceToUse = FindMatchingItemByName -EndPoint "$runbookBaseUrl/api/spaces" -NameToLookFor $runbookSpaceName -ItemType "Space" -APIKey $runbookApiKey -PullFirstItem $false
$runbookSpaceId = $spaceToUse.Id

$environmentToUse = FindMatchingItemByName -EndPoint "$runbookBaseUrl/api/$runbookSpaceId/environments" -NameToLookFor $runbookEnvironmentName -ItemType "Environment" -APIKey $runbookApiKey -PullFirstItem $false
$environmentIdToUse = $environmentToUse.Id

$tenantIdToUse = $null
if ([string]::IsNullOrWhiteSpace($runbookTenantName) -eq $false)
{
	$tenantToUse = FindMatchingItemByName -EndPoint "$runbookBaseUrl/api/$runbookSpaceId/tenants" -NameToLookFor $runbookTenantName -ItemType "Tenant" -APIKey $runbookApiKey -PullFirstItem $false
    $tenantIdToUse = $tenantToUse.Id
}

$projectToUse = FindMatchingItemByName -EndPoint "$runbookBaseUrl/api/$runbookSpaceId/projects" -NameToLookFor $runbookProjectName -ItemType "Environment" -APIKey $runbookApiKey -PullFirstItem $false
$projectIdToUse = $projectToUse.Id

$runbookToRun = FindMatchingItemByName -EndPoint "$runbookBaseUrl/api/$runbookSpaceId/projects/$projectIdToUse/runbooks" -NameToLookFor $runbookRunName -ItemType "Runbook" -APIKey $runbookApiKey -PullFirstItem $false
$runbookIdToRun = $runbookToRun.Id
$runbookProjectId = $runbookToRun.ProjectId
$runbookSnapShotIdToUse = $runbookToRun.PublishedRunbookSnapshotId

if ($runbookSnapShotIdToUse -eq $null -and $runbookUsePublishedSnapshot -eq $true)
{
    Write-Host "Use Published Snapshot was set; yet the runbook doesn't have a published snapshot.  Exiting"
    Exit 1
}

if ($runbookUsePublishedSnapshot -eq $false)
{
	$snapShotToUse = FindMatchingItemByName -EndPoint "$runbookBaseUrl/api/$runbookSpaceId/runbooks/$runbookIdToRun/runbookSnapshots" -NameToLookFor "" -ItemType "Snapshot" -APIKey $runbookApiKey -PullFirstItem $true
    $runbookSnapShotIdToUse = $snapShotToUse.Id        
}

$projectResponse = Invoke-RestMethod "$runbookBaseUrl/api/$runbookSpaceId/projects/$runbookProjectId" -Headers $header
$projectNameForUrl = $projectResponse.Slug

$runbookFormValues = @{}
if ([string]::IsNullOrWhiteSpace($runbookPromptedVariables) -eq $false)
{
	$runBookPreviewUrl = "$runbookBaseUrl/api/$runbookSpaceId/runbooks/$runbookIdToRun/runbookRuns/preview/$environmentIdToUse"
    Write-Host "Prompted variables were supplied, hitting the preview endpoint $runbookPreviewUrl"
	$runBookPreview = Invoke-RestMethod $runbookPreviewUrl -Headers $header    
    
    $promptedValueList = @(($runbookPromptedVariables -Split "`n").Trim())
    Write-Host $promptedValueList.Length
    
    foreach($element in $runbookPreview.Form.Elements)
    {
    	$nameToSearchFor = $element.Control.Name
        $uniqueName = $element.Name
        $isRequired = $element.Control.Required
        
        $promptedVariableFound = $false
        
        Write-Host "Looking for the prompted variable value for $nameToSearchFor"
    	foreach ($promptedValue in $promptedValueList)
        {
        	$splitValue = $promptedValue -Split "::"
            Write-Host "Comparing $nameToSearchFor with provided prompted variable $($splitValue[0])"
            if ($splitValue.Length -gt 1)
            {
            	if ($nameToSearchFor -eq $splitValue[0])
                {
                	Write-Host "Found the prompted variable value $nameToSearchFor"
                	$runbookFormValues[$uniqueName] = $splitValue[1]
                    $promptedVariableFound = $true
                    break
                }
            }
        }
        
        if ($promptedVariableFound -eq $false -and $isRequired -eq $true)
        {
        	Write-Host "Unable to find a value for the required prompted variable $nameToSearchFor, exiting"
            Exit 1
        }
    }
}

$runbookBody = @{
    RunbookId = $runbookIdToRun;
    RunbookSnapShotId = $runbookSnapShotIdToUse;
    FrozenRunbookProcessId = $null;
    EnvironmentId = $environmentIdToUse;
    TenantId = $tenantIdToUse;
    SkipActions = @();
    QueueTime = $null;
    QueueTimeExpiry = $null;
    FormValues = $runbookFormValues;
    ForcePackageDownload = $false;
    ForcePackageRedeployment = $true;
    UseGuidedFailure = $runbookUseGuidedFailure;
    SpecificMachineIds = @();
    ExcludedMachineIds = @()
}

$runbookBodyAsJson = $runbookBody | ConvertTo-Json
$runbookPostUrl = "$runbookBaseUrl/api/$runbookSpaceId/runbookRuns"
Write-Host "Kicking off runbook run by posting to $runbookPostUrl"

$runBookResponse = Invoke-RestMethod $runbookPostUrl -Method POST -Headers $header -Body $runbookBodyAsJson
$runbookServerTaskId = $runBookResponse.TaskId
$runbookRunId = $runbookResponse.Id

Write-Host "Runbook was successfully invoked, you can access the launched runbook [here]($runbookBaseUrl/app#/$runbookSpaceId/projects/$projectNameForUrl/operations/runbooks/$runbookIdToRun/snapshots/$runbookSnapShotIdToUse/runs/$runbookRunId)"
if ($runbookWaitForFinish -eq $true)
{
	Write-Host "The setting to wait for completion was set, waiting until task has finished"
    $startTime = Get-Date
    $currentTime = Get-Date
    $dateDifference = $currentTime - $startTime
	
    $taskStatusUrl = "$runbookBaseUrl/api/tasks/$runbookServerTaskId"
    $numberOfWaits = 0    
    
    $runbookSuccessful = $null

    While ($dateDifference.TotalSeconds -lt $runbookCancelInSeconds)
    {
        Write-Host "Waiting 5 seconds to check status"
        Start-Sleep -Seconds 5
        $taskStatusResponse = Invoke-RestMethod $taskStatusUrl -Headers $header        
        $taskStatusResponseState = $taskStatusResponse.State

        if ($taskStatusResponseState -eq "Success")
        {
            Write-Host "The task has finished with a status of Success"
            $runbookSuccessful = $true            
            break
        }
        elseif($taskStatusResponseState -eq "Failed" -or $taskStatusResponseState -eq "Canceled")
        {
            Write-Host "The task has finished with a status of $taskStatusResponseState status, stopping the run/deployment"
            $runbookSuccessful = $false
            break            
        }        

        Write-Host "The task state is currently $taskStatusResponseState"
        
        $startTime = $taskStatusResponse.StartTime
        if ($startTime -eq $null)
        {        
        	Write-Host "The task is still queued, let's wait a bit longer"
        	$startTime = Get-Date
        }
        $startTime = [DateTime]$startTime
        
        $currentTime = Get-Date
        $dateDifference = $currentTime - $startTime        
    }

    if ($null -eq $runbookSuccessful)
    {
        Write-Host "The cancel timeout has been reached, cancelling the runbook run"
        $cancelResponse = Invoke-RestMethod "$runbookBaseUrl/api/tasks/$runbookServerTaskId/cancel" -Headers $header -Method Post
        Write-Host "Exiting with an error code of 1 because we reached the timeout"
    }
}
C#
// If using .net Core, be sure to add the NuGet package of System.Security.Permissions
#r "path\to\Octopus.Client.dll"

using Octopus.Client;
using Octopus.Client.Model;

var octopusURL = "https://your-octopus-url";
var octopusAPIKey = "API-YOUR-KEY";
string spaceName = "Default";
string environmentName = "Development";
string runbookName = "Runbook name";

// Leave blank if you'd like to use the published snapshot
string runbookSnapshotId = "";

Dictionary<string, string> promptedVariables = new Dictionary<string, string>();
// Enter multiple values using the .Add() method
// promptedVariables.Add("prompted-variable1", "variable1-value")

// Create repository object
var endpoint = new OctopusServerEndpoint(octopusURL, octopusAPIKey);
var repository = new OctopusRepository(endpoint);
var client = new OctopusClient(endpoint);

try
{
    // Get space
    var space = repository.Spaces.FindByName(spaceName);
    var repositoryForSpace = client.ForSpace(space);

    // Get runbook
    var runbook = repositoryForSpace.Runbooks.FindOne(n => n.Name == runbookName);

    // Get environment
    var environment = repositoryForSpace.Environments.FindByName(environmentName);

    // Use published snapshot if no id provided
    if (string.IsNullOrWhiteSpace(runbookSnapshotId))
    {
        runbookSnapshotId = runbook.PublishedRunbookSnapshotId;
    }

    var runbookTemplate = repositoryForSpace.Runbooks.GetRunbookRunTemplate(runbook);
    var deploymentPromotionTarget = runbookTemplate.PromoteTo.FirstOrDefault(p => p.Name == environmentName);
    var runbookPreview = repositoryForSpace.Runbooks.GetPreview(deploymentPromotionTarget);

    var formValues = new Dictionary<string, string>();
    
    // Associate variable values for the runbook
    foreach (var variableName in promptedVariables.Keys)
    {
        var element = runbookPreview.Form.Elements.FirstOrDefault(e => (e.Control as Octopus.Client.Model.Forms.VariableValue).Name == variableName);
        if (element != null)
        {
            var runbookPromptedVariableId = element.Name;
            var runbookPromptedVariableValue = promptedVariables[variableName];
            formValues.Add(runbookPromptedVariableId, runbookPromptedVariableValue);
        }
    }

    // Create runbook run object
    Octopus.Client.Model.RunbookRunResource runbookRun = new RunbookRunResource();

    runbookRun.EnvironmentId = environment.Id;
    runbookRun.RunbookId = runbook.Id;
    runbookRun.ProjectId = runbook.ProjectId;
    runbookRun.RunbookSnapshotId = runbookSnapshotId;
    runbookRun.FormValues = formValues;
    // Execute runbook
    repositoryForSpace.RunbookRuns.Create(runbookRun);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    return;
}

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