How can I export multiple projects from Octopus Deploy?

I want to be able to export multiple projects from my Octopus instance.

I’d also ideally like to script this so it involves no user interaction.

How can I do this?

This is possible using the Export/Import Projects feature that was added in Octopus Deploy 2021.1.

The advantage of using this feature is that an export will contain nearly all of the dependencies required to successfully deploy a project, including sensitive variable values too.

:warning: Don’t use the Export/Import Projects feature as a substitute for a comprehensive backup/restore process

Automating the export and import of projects using the REST API as part of a backup/restore process is not recommended. See our supported scenarios for further information.

You can export and import projects from within the Octopus Web Portal. However, since the Web Portal is built on top of the Octopus REST API, you can also export projects using this too.

You can interact with the REST API by writing a script in whichever language you are comfortable with.
The following example is written in PowerShell:

$ErrorActionPreference = "Stop";

# Define working variables
$octopusURL = "https://your.octopus.app/"
$octopusAPIKey = "API-KEY"
$header = @{ "X-Octopus-ApiKey" = $octopusAPIKey }

# Provide the space name for the projects to export.
$spaceName = "Default"
# Provide a list of project names to export.
$projectNames = @("Project A", "Project B")
# Provide a password for the export zip file
$exportTaskPassword = ""
# Wait for the export task to finish?
$exportTaskWaitForFinish = $True
# Provide a timeout for the export task to be canceled.
$exportTaskCancelInSeconds=300

$octopusURL = $octopusURL.TrimEnd('/')

# Get Space
$spaces = Invoke-RestMethod -Uri "$octopusURL/api/spaces?partialName=$([uri]::EscapeDataString($spaceName))&skip=0&take=100" -Headers $header 
$space = $spaces.Items | Where-Object { $_.Name -eq $spaceName }
$exportTaskSpaceId = $space.Id

$exportTaskProjectIds = @()

if (![string]::IsNullOrWhiteSpace($projectNames)) {
    @(($projectNames -Split "`n").Trim()) | ForEach-Object {
        if (![string]::IsNullOrWhiteSpace($_)) {
            Write-Verbose "Working on: '$_'"
            $projectName = $_.Trim()
            if([string]::IsNullOrWhiteSpace($projectName)) {
                throw "Project name is empty'"
            }
            $projects = Invoke-RestMethod -Uri "$octopusURL/api/$($space.Id)/projects?partialName=$([uri]::EscapeDataString($projectName))&skip=0&take=100" -Headers $header 
			$project = $projects.Items | Where-Object { $_.Name -eq $projectName }
            $exportTaskProjectIds += $project.Id
        }
    }
}

$exportBody = @{
    IncludedProjectIds = $exportTaskProjectIds;
    Password = @{
    	HasValue = $True;
        NewValue = $exportTaskPassword;
    }
}

$exportBodyAsJson = $exportBody | ConvertTo-Json
$exportBodyPostUrl = "$octopusURL/api/$($exportTaskSpaceId)/projects/import-export/export"
Write-Host "Kicking off export run by posting to $exportBodyPostUrl"
Write-Verbose "Payload: $exportBodyAsJson"
$exportResponse = Invoke-RestMethod $exportBodyPostUrl -Method POST -Headers $header -Body $exportBodyAsJson
$exportServerTaskId = $exportResponse.TaskId
Write-Host "The task id of the new task is $exportServerTaskId"
Write-Host "Export task was successfully invoked, you can access the task: $octopusURL/app#/$exportTaskSpaceId/tasks/$exportServerTaskId"

if ($exportTaskWaitForFinish -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 = "$octopusURL/api/$exportTaskSpaceId/tasks/$exportServerTaskId"
    $numberOfWaits = 0    
    While ($dateDifference.TotalSeconds -lt $exportTaskCancelInSeconds)
    {
        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"
            $artifactsUrl= "$octopusURL/api/$exportTaskSpaceId/artifacts?regarding=$exportServerTaskId"
            Write-Host "Checking for artifacts from $artifactsUrl"
            $artifacts = Invoke-RestMethod $artifactsUrl -Method GET -Headers $header
            $exportArtifact = $artifacts.Items | Where-Object { $_.Filename -like "Octopus-Export-*.zip"} 
            Write-Host "Export task successfully completed, you can download the export archive: $octopusURL$($exportArtifact.Links.Content)"
            exit 0
        }
        elseif($taskStatusResponseState -eq "Failed" -or $taskStatusResponseState -eq "Canceled")
        {
            Write-Host "The task has finished with a status of $taskStatusResponseState status, completing"
            exit 1            
        }
        $numberOfWaits += 1
        if ($numberOfWaits -ge 10)
        {
        	Write-Host "The task state is currently $taskStatusResponseState"
        	$numberOfWaits = 0
        }
        else
        {
        	Write-Host "The task state is currently $taskStatusResponseState"
        }  
        $startTime = $taskStatusResponse.StartTime
        if ($null -eq $startTime -or [string]::IsNullOrWhiteSpace($startTime) -eq $true)
        {        
        	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        
    }
    Write-Host "The cancel timeout has been reached, cancelling the export task"
    Invoke-RestMethod "$octopusURL/api/$exportTaskSpaceId/tasks/$exportTaskSpaceId/cancel" -Headers $header -Method Post | Out-Null
    Write-Host "Exiting with an error code of 1 because we reached the timeout"
    exit 1
}

With this script, you provide values for:

  • The Octopus URL
  • The Octopus API Key
  • The name of the space where the projects to be exported can be found
  • A list of project names to be exported
  • A password to protect sensitive values in the exported data
  • A boolean whether or not to wait for the export task to finish
    • A timeout in seconds to wait before attempting to cancel the task.

To take this one step further, you could also add this script to an Octopus runbook to export the project as well:

:white_check_mark: You can see the example script provided here and more in our Octopus REST API Examples.