Import projects

This script demonstrates how you can import projects into an Octopus space. It uses a previously executed export task from another space as the source for the import.

Note: Please note there are some items to consider before using this script:

  • This script uses an API endpoint introduced in Octopus 2021.1 for the Export/Import Projects feature. Using this script in earlier versions of Octopus will not work.
  • Automating the import of projects as part of a backup/restore process is not recommended. See our supported scenarios when using the API from this feature.

Usage

Provide values for the following:

  • Octopus URL
  • Octopus API Key
  • Name of the space where the projects were exported from
  • Name of the space where the projects are to be exported into
  • The export Server Task Id to use as the source for import e.g. ServerTasks-12345
  • The password used to protect sensitive values in the exported data
  • Boolean whether or not to wait for the import task to finish
    • Timeout in seconds to wait before attempting to cancel the task.

Script

PowerShell (REST API)
$ErrorActionPreference = "Stop";

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

# Provide the space name where the export task ran.
$sourceSpaceName = "Export-Space"
# Provide the space name for the projects to be imported into.
$destinationSpaceName = "Default"
# Provide the Export Server task id to use as the source for import e.g. ServerTasks-12345
$exportTaskId = ""
# Provide a password for the import zip file
$importTaskPassword = ""
# Wait for the import task to finish?
$importTaskWaitForFinish = $True
# Provide a timeout for the imports task to be canceled.
$importTaskCancelInSeconds=300

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

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

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

$importBody = @{
    ImportSource = @{
    	Type = "space";
        SpaceId = $exportTaskSpaceId;
        TaskId = $exportTaskId;
    };
    Password = @{
    	HasValue = $True;
        NewValue = $importTaskPassword;
    };
}

$importBodyAsJson = $importBody | ConvertTo-Json
$importBodyPostUrl = "$octopusURL/api/$($importTaskSpaceId)/projects/import-export/import"
Write-Host "Kicking off import run by posting to $importBodyPostUrl"
Write-Verbose "Payload: $importBodyAsJson"
$importResponse = Invoke-RestMethod $importBodyPostUrl -Method POST -Headers $header -Body $importBodyAsJson
$importServerTaskId = $importResponse.TaskId
Write-Host "The task id of the new task is $importServerTaskId"
Write-Host "Import task was successfully invoked, you can access the task: $octopusURL/app#/$importTaskSpaceId/tasks/$importServerTaskId"

if ($importTaskWaitForFinish -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/$importTaskSpaceId/tasks/$importServerTaskId"
    $numberOfWaits = 0    
    While ($dateDifference.TotalSeconds -lt $importTaskCancelInSeconds)
    {
        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"
            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 import task"
    Invoke-RestMethod "$octopusURL/api/$importTaskSpaceId/tasks/$importTaskSpaceId/cancel" -Headers $header -Method Post | Out-Null
    Write-Host "Exiting with an error code of 1 because we reached the timeout"
    exit 1
}
Python3
import json
import requests
from requests.api import get, head
from datetime import datetime
import time

def get_octopus_resource(uri, headers, skip_count = 0):
    items = []
    skip_querystring = ""

    if '?' in uri:
        skip_querystring = '&skip='
    else:
        skip_querystring = '?skip='

    response = requests.get((uri + skip_querystring + str(skip_count)), headers=headers)
    response.raise_for_status()

    # Get results of API call
    results = json.loads(response.content.decode('utf-8'))

    # Store results
    if hasattr(results, 'keys') and 'Items' in results.keys():
        items += results['Items']

        # Check to see if there are more results
        if (len(results['Items']) > 0) and (len(results['Items']) == results['ItemsPerPage']):
            skip_count += results['ItemsPerPage']
            items += get_octopus_resource(uri, headers, skip_count)

    else:
        return results

    
    # return results
    return items

octopus_server_uri = 'https://your-octopus-url'
octopus_api_key = 'API-YOUR-KEY'
headers = {'X-Octopus-ApiKey': octopus_api_key}
sourceSpaceName = "Default"
destinationSpaceName = "DestinationSpace"
exportTaskId = "ServerTasks-XXXX" # from the export operation
importTaskPassword = "MyFantasticPassword"
importTaskWaitForFinish = True
importTaskCancelInSeconds = 300

# Get destination space
uri = '{0}/api/spaces'.format(octopus_server_uri)
spaces = get_octopus_resource(uri, headers)
destinationSpace = next((x for x in spaces if x['Name'] == destinationSpaceName), None)

# Get source space
sourceSpace = next((x for x in spaces if x['Name'] == sourceSpaceName), None)

# Define body of request
importBody = {
    'ImportSource': {
        'Type': 'space', # must be lower case
        'SpaceId': sourceSpace['Id'],
        'TaskId' : exportTaskId
    },
    'Password': {
        'HasValue': True,
        'NewValue': importTaskPassword
    }
}

# Execute transfer
uri = '{0}/api/{1}/projects/import-export/import'.format(octopus_server_uri, destinationSpace['Id'])
print ('Kicking off import from {0} to {1}'.format(sourceSpaceName, destinationSpaceName))
response = requests.post(uri, headers=headers, json=importBody)
response.raise_for_status()

# Get results
results = json.loads(response.content.decode('utf-8'))
importTaskId = results['TaskId']
print ('The task id of the new task is: {0}'.format(importTaskId))

if importTaskWaitForFinish:
    start_time = datetime.now()
    current_time = datetime.now()
    
    date_difference = current_time - start_time
    number_of_waits = 0

    while date_difference.seconds < importTaskCancelInSeconds:
        print ('Waiting 5 seconds')
        time.sleep(5)
        uri = '{0}/api/{1}/tasks/{2}'.format(octopus_server_uri, destinationSpace['Id'], importTaskId)
        response = requests.get(uri, headers=headers)
        response.raise_for_status()

        results = json.loads(response.content.decode('utf-8'))
        
        if results['State'] == 'Success':
            print ('The task has finished successfully')
            exit(0)

        elif results['State'] == 'Failed' or results['State'] == 'Cancelled':
            print ('The task finished with a status of {0}'.format(results['State']))
            exit(1)

        number_of_waits += 1

        if number_of_waits >= 10:
            print ('The task is currently {0}'.format(results['State']))
            number_of_waits = 0
        else:
            print ('The task is currently {0}'.format(results['State']))
        
        if results['StartTime'] == None or results['StartTime'] == '':
            print ('The task is still queued, let us wait a bit longer')
            start_time = datetime.now()
        
        current_time = datetime.now()
        date_difference = current_time - start_time    

  
    print ('The cancel timeout has been reached, cancelling the export task')
    uri = '{0}/api/{1}/tasks/{2}'.format(octopus_server_uri, destinationSpace['Id'], importTaskId)
    response = requests.get(uri, headers=headers)
    response.raise_for_status()
Go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"time"

	"github.com/OctopusDeploy/go-octopusdeploy/octopusdeploy"
)

type ImportProject struct {
	ImportSource ImportSource
	Password     *octopusdeploy.SensitiveValue
}

type ImportSource struct {
	Type    string
	SpaceId string
	TaskId  string
}

func main() {

	apiURL, err := url.Parse("https://your-octopus-url")
	if err != nil {
		log.Println(err)
	}
	APIKey := "API-YOUR-KEY"
	destinationSpaceName := "Destination Space"
	exportPassword := "MyFantasticPassword"
	exportTaskId := "ServerTasks-XXXXX"

	// Get reference to space
	destinationSpace := GetSpace(apiURL, APIKey, destinationSpaceName)


	// Build body
	password := octopusdeploy.NewSensitiveValue(exportPassword)
	importObject := ImportProject{}
	importObject.ImportSource.SpaceId = destinationSpace.ID
	importObject.ImportSource.TaskId = exportTaskId
	importObject.ImportSource.Type = "space"
	importObject.Password = password

	// Export the projects
	ImportProjects(apiURL, APIKey, destinationSpace, importObject, true, 300)

}

func octopusAuth(octopusURL *url.URL, APIKey, space string) *octopusdeploy.Client {
	client, err := octopusdeploy.NewClient(nil, octopusURL, APIKey, space)
	if err != nil {
		log.Println(err)
	}

	return client
}

func GetSpace(octopusURL *url.URL, APIKey string, spaceName string) *octopusdeploy.Space {
	client := octopusAuth(octopusURL, APIKey, "")

	spaceQuery := octopusdeploy.SpacesQuery{
		Name: spaceName,
	}

	// Get specific space object
	spaces, err := client.Spaces.Get(spaceQuery)

	if err != nil {
		log.Println(err)
	}

	for _, space := range spaces.Items {
		if space.Name == spaceName {
			return space
		}
	}

	return nil
}

func GetProject(octopusURL *url.URL, APIKey string, space *octopusdeploy.Space, projectName string) *octopusdeploy.Project {
	// Create client
	client := octopusAuth(octopusURL, APIKey, space.ID)

	projectsQuery := octopusdeploy.ProjectsQuery {
		Name: projectName,
	}

	// Get specific project object
	projects, err := client.Projects.Get(projectsQuery)

	if err != nil {
		log.Println(err)
	}

	for _, project := range projects.Items {
		if project.Name == projectName {
			return project
		}
	}

	return nil
}

func ImportProjects(octopusURL *url.URL, APIKey string, space *octopusdeploy.Space, importProjects ImportProject, waitForFinish bool, taskCancelInSeconds int) {
	// Create http client
	httpClient := &http.Client{}
	exportTaskUrl := octopusURL.String() + "/api/" + space.ID + "/projects/import-export/import"

	// Make request
	jsonBody, err := json.Marshal(importProjects)
	myString := string(jsonBody)
	fmt.Println(myString)
	if err != nil {
		log.Println(err)
	}

	request, _ := http.NewRequest("POST", exportTaskUrl, bytes.NewBuffer(jsonBody))
	request.Header.Set("X-Octopus-ApiKey", APIKey)
	response, err := httpClient.Do(request)

	if err != nil {
		log.Println(err)
	}

	responseData, err := ioutil.ReadAll(response.Body)

	var serverTaskRaw interface{}
	jsonErr := json.Unmarshal(responseData, &serverTaskRaw)
	if jsonErr != nil {
		log.Println(err)
	}

	// Get the task id
	serverTask := serverTaskRaw.(map[string]interface{})
	serverTaskId := serverTask["TaskId"]
	fmt.Println("The task id of the new task is: " + serverTaskId.(string))

	if waitForFinish {
		fmt.Println("The setting to wait for completion was set, waiting until the task has finished")

		elapsedSeconds := 0
		taskUrl := octopusURL.String() + "/api/" + space.ID + "/tasks/" + serverTaskId.(string)

		for elapsedSeconds < taskCancelInSeconds {
			time.Sleep(5 * time.Second)
			elapsedSeconds += 5

			request, _ := http.NewRequest("GET", taskUrl, nil)
			request.Header.Set("X-Octopus-ApiKey", APIKey)
			response, err := httpClient.Do(request)

			if err != nil {
				log.Println(err)
			}

			responseData, err := ioutil.ReadAll(response.Body)

			var serverTaskRaw interface{}
			jsonErr := json.Unmarshal(responseData, &serverTaskRaw)
			if jsonErr != nil {
				log.Println(err)
			}
			serverTask = serverTaskRaw.(map[string]interface{})
			taskStatus := serverTask["State"]

			if taskStatus.(string) == "Success" {
				fmt.Println("The task has finished successfully")
				break

			} else if taskStatus.(string) == "Failed" || taskStatus.(string) == "Cancelled" {
				fmt.Println("The task finished with a status of " + taskStatus.(string))
				break
			}
		}

		if elapsedSeconds >= taskCancelInSeconds {
			cancelUrl := octopusURL.String() + "/api/" + space.ID + "/tasks/" + serverTaskId.(string) + "/cancel"
			request, _ := http.NewRequest("GET", cancelUrl, nil)
			request.Header.Set("X-Octopus-ApiKey", APIKey)
			response, err := httpClient.Do(request)

			if err != nil {
				log.Println(err)
			}
			fmt.Println(response)
		}
	}
}

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