Andreas Huhta
Jan 4, 2021
  9998
(7 votes)

Octopus Deploy and Episerver's deployment API - Quickstart guide

The intention of this post is to allow anyone who has limited knowledge in how to use the Deployment API to quickly start deploying from their Octopus Deploy server to their DXP environments using the deployment API along with some additional features that can also be actioned through the deployment API.

There are of course many customizations that can be done here, both on the lifecycle and the deployment process side.
But to keep it short and simple I've decided to write this guide as if it was for someone who simply wants to spend minimal effort in order to be able to hit a button in Octopus and deploy.

This guide includes following processes:

Prerequisites:

  • You have generated a client key and secret from the PaaS portal's API tab.
  • Your Octopus Deploy server should already be installed and with at least a version that is still supported by Octopus and be able to communicate externally.
  • The NuGet package provider should be installed on your Octopus Deploy server, otherwise installing the EpiCloud powershell module will most likely fail to install.
    Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force​​
  • You have at least one empty Environment in Octopus Deploy, in the examples below I'm only using one called DXP which has no deployment targets

Deployment using package as source

There are multiple deployment processes that can be used in DXP. Standard deployment, maintenance page, smooth deploy.
This template allows you to easily select what options you want to use in your deployment.

No matter what you select though, every deployment process in DXP has two steps.
Start Deployment and Complete deployment. The purpose of this is to have a manual intervention between the phases so you can confirm that the new code is working as expected in the slot before completing the deployment.

If you don't require to have any manual intervention, for example when you deploy to integration or preproduction, select "Yes" on the "Auto Complete" option in the process step. Going with this approach you won't need a "complete deployment" step.

If you leave it as "No", then you must import both templates and create two process steps in your project. I strongly suggest manual interventions in production deployments so you don't accidentally go live with a broken application.

Import the following step templates into Library > Step Templates

Deploy package

{
"Id": "5311c5fc-19aa-47a7-990b-a0349863a7b5",
"Name": "Episerver DXP - Deploy Package",
"Description": "Deploy a package to your Episerver DXP environment.",
"ActionType": "Octopus.Script",
"Version": 1,
"CommunityActionTemplateId": null,
"Packages": [
{
"Id": "92ca1df3-e52c-4019-a610-421af7196770",
"Name": "sourcepackage",
"PackageId": null,
"FeedId": "Feeds-1021",
"AcquisitionLocation": "Server",
"Properties": {
"Extract": "False",
"SelectionMode": "deferred",
"PackageParameterName": "sourcepackage"
}
}
],
"Properties": {
"Octopus.Action.Script.ScriptSource": "Inline",
"Octopus.Action.Script.Syntax": "PowerShell",
"Octopus.Action.Script.ScriptBody": "$packagepath = $OctopusParameters[\"Octopus.Action.Package[sourcepackage].OriginalPath\"]\n$packagename = Split-Path $packagepath -leaf\n\nif (-not (Get-Module -Name EpiCloud -ListAvailable)) {\n Install-Module EpiCloud -Scope CurrentUser -Force\n}\n\nConnect-EpiCloud -ClientKey $ClientKey -ClientSecret $ClientSecret -ProjectId $ProjectId\n$saslink = Get-EpiDeploymentPackageLocation\nAdd-EpiDeploymentPackage -SasUrl $saslink -Path $packagepath\n\nWrite-Host \"Package uploaded - Starting deployment to $targetenvironment\"\nWrite-Host \"You can see further progress by changing log level to Verbose\"\n\n$deployments = Get-EpiDeployment -ProjectId $ProjectId\n$ongoingdeployid = 0\nForeach ($deployment in $deployments) {\n if ($deployment.parameters.targetEnvironment -eq $targetenvironment ) {\n \tif ($deployment.status -eq 'AwaitingVerification' -Or $deployment.status -eq 'InProgress' ) {\n $ongoingdeployid = $deployment.id\n Write-Host $deployment\n break\n      \t}\n }\n}\n$startEpiDeploymentSplat = @{\n ProjectId = $ProjectId\n TargetEnvironment = $targetenvironment\n\t\tDeploymentPackage = $packagename\n UseMaintenancePage = [System.Convert]::ToBoolean($OctopusParameters['maintenancepage'])\n}\nif ([System.Convert]::ToBoolean($OctopusParameters['smoothdeploy'])) {\n $startEpiDeploymentSplat.Add('ZeroDowntimeMode', 'ReadOnly')\n}\n\nif ($ongoingdeployid -eq 0){\n $deployment = Start-EpiDeployment @startEpiDeploymentSplat -Wait\n if ($autocomplete -eq 'true') {\n $deployment | Complete-EpiDeployment -Wait\n }\n}\nelse {\n Write-Warning \"There is already an ongoing deployment in progress ($ongoingdeployid). Complete or reset the ongoing deployment then try this again.\" \n}"
},
"Parameters": [
{
"Id": "caab69c6-35fa-48ad-b561-f49b79d49c9f",
"Name": "ProjectId",
"Label": "Project ID",
"HelpText": "Your DXP project ID can easily be found in the API tab within the PaaS portal.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "d452e8a7-ddbb-484c-bbd7-353d276e3261",
"Name": "ClientKey",
"Label": "Client Key",
"HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "081ad644-04bc-47a7-a241-df85b7aaca1b",
"Name": "ClientSecret",
"Label": "Client Secret",
"HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "Sensitive"
}
},
{
"Id": "39194e0d-9f4f-428d-88a8-d2624d1f4908",
"Name": "sourcepackage",
"Label": "Package Name",
"HelpText": "Specify the package you want to deploy",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "Package"
}
},
{
"Id": "e61b1e60-eb96-41ef-8670-99713933014e",
"Name": "targetenvironment",
"Label": "Target Environment",
"HelpText": "Select the environment that the package should be deployed to",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "Select",
"Octopus.SelectOptions": "Integration|Integration\nPreproduction|Preproduction\nProduction|Production"
}
},
{
"Id": "ce603470-4c9b-4e88-99ca-384c5df95a3f",
"Name": "maintenancepage",
"Label": "Maintenance Page",
"HelpText": "Select if you want this deployment to use a maintenance page.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "Select",
"Octopus.SelectOptions": "false|No\ntrue|Yes"
}
},
{
"Id": "46b20db9-b3d0-4030-9370-752e2cba2074",
"Name": "smoothdeploy",
"Label": "Smooth Deploy",
"HelpText": "Select if you want this deployment to use Smooth Deploy process.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "Select",
"Octopus.SelectOptions": "false|No\ntrue|Yes"
}
},
{
"Id": "a54f0633-c3de-437e-82dc-7a747bb615fc",
"Name": "autocomplete",
"Label": "Auto Complete",
"HelpText": "Automatically complete the deployment once the first phase is completed.\nThis is not recommended for production deployments.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "Select",
"Octopus.SelectOptions": "false|No\ntrue|Yes"
}
}
],
"$Meta": {
"ExportedAt": "2021-01-04T10:29:46.288Z",
"OctopusVersion": "2020.4.11",
"Type": "ActionTemplate"
},
"LastModifiedBy": "Your GitHub Username",
"Category": "other"
}

Complete deployment

{
"Id": "f0e4c038-de0c-4478-99b4-527af20eb3de",
"Name": "Episerver DXP - Complete deploy",
"Description": "Deploy to a DXP environment using another environment in the same project as source",
"ActionType": "Octopus.Script",
"Version": 1,
"CommunityActionTemplateId": null,
"Packages": [],
"Properties": {
"Octopus.Action.Script.ScriptSource": "Inline",
"Octopus.Action.Script.Syntax": "PowerShell",
"Octopus.Action.Script.ScriptBody": "if (-not (Get-Module -Name EpiCloud -ListAvailable)) {\n Install-Module EpiCloud -Scope CurrentUser -Force\n}\n\nConnect-EpiCloud -ClientKey $ClientKey -ClientSecret $ClientSecret -ProjectId $ProjectId\n\n$deployments = Get-EpiDeployment -ProjectId $ProjectId\n$ongoingdeployid = 0\nForeach ($deployment in $deployments) {\n if ($deployment.parameters.targetEnvironment -eq $targetenvironment ) {\n \tif ($deployment.status -eq 'AwaitingVerification') {\n $ongoingdeployid = $deployment.id\n break\n \t}\n }\n}\n\nif ($ongoingdeployid -eq 0) {\n\tWrite-Error \"Unable to find a deployment with status 'AwaitingVerification', you can easily check in the PaaS portal web interface if you have any ongoing deployments.\"\n}\nelse {\n\tComplete-EpiDeployment -ProjectId $ProjectId -Id $ongoingdeployid -Wait\n}\n"
},
"Parameters": [
{
"Id": "caab69c6-35fa-48ad-b561-f49b79d49c9f",
"Name": "ProjectId",
"Label": "Project ID",
"HelpText": "Your DXP project ID can easily be found in the API tab within the PaaS portal.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "d452e8a7-ddbb-484c-bbd7-353d276e3261",
"Name": "ClientKey",
"Label": "Client Key",
"HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "081ad644-04bc-47a7-a241-df85b7aaca1b",
"Name": "ClientSecret",
"Label": "Client Secret",
"HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "Sensitive"
}
},
{
"Id": "e61b1e60-eb96-41ef-8670-99713933014e",
"Name": "targetenvironment",
"Label": "Target Environment",
"HelpText": "Select the environment that the package should be deployed to",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "Select",
"Octopus.SelectOptions": "Integration|Integration\nPreproduction|Preproduction\nProduction|Production"
}
}
],
"$Meta": {
"ExportedAt": "2020-12-20T12:17:02.612Z",
"OctopusVersion": "2020.4.11",
"Type": "ActionTemplate"
},
"LastModifiedBy": "Your GitHub Username",
"Category": "other"
}

To set up the first (Package Deploy) step, add it as a project process. Then all you have to do is specify the following properties:

  1. Executed on the Octopus Server.
  2. Project ID which you can find in the PaaS portal API tab.
  3. Your client key.
  4. Your client secret.
  5. The package from your built-in repository.
  6. What environment you want to deploy to.
  7. Select if maintenance page should be used in the deployment.
  8. Select if you want to use the smooth deploy process.
  9. Select if you want to auto complete the deployment.

The complete deploy step is pretty much the same process:

  1. Executed on the Octopus Server.
  2. Project ID which you can find in the PaaS portal API tab.
  3. Your client key.
  4. Your client secret.
  5. What environment you deployed to.

Deploying a package from your built-in repository in Octopus works best if you configure your project to automatically create new releases based on the deployment step.
Once you've added the Package Deployment process step then you can go into your project's settings and specify that it should use a version from the selected package.

Then go into Triggers and enable automatic release creation.

Your new releases should now be automatically created.

There are of course much more you can do such as customizing your lifecycle and environments.
Using project variables as values instead of the predefined ones that the template offers.

You can also build in your own checks and reset process steps between the phases. These templates are just the tip of the iceberg and a foundation to get you started.

Known limitations

  • You can't include database or blobs as there is no source environment defined.
  • You can only run one deployment at a time within the same DXP project.
  • Your code package must follow the format stated in this article.

Deployment using environment as source

There are multiple deployment processes that can be used in DXP. Standard deployment, maintenance page, smooth deploy, including content. 
This template allows you to easily select what options you want to use in your deployment.

No matter what you select though, every deployment process in DXP has two steps.
Start Deployment and Complete deployment. The purpose of this is to have a manual intervention between the phases so you can confirm that the new code is working as expected in the slot before completing the deployment.

If you don't require to have any manual intervention, for example when you deploy to integration or preproduction, select "Yes" on the "Auto Complete" option in the process step. Going with this approach you won't need a "complete deployment" step.

If you leave it as "No", then you must import both templates and create two process steps in your project. I strongly suggest manual interventions in production deployments so you don't accidentally go live with a broken application.

Import the following step templates into Library > Step Templates

Start deployment

{
  "Id": "e9b263ca-41ec-4576-8f68-c2708d27dbb3",
  "Name": "Episerver DXP - Start deploy from Environment",
  "Description": "Deploy to a DXP environment using another environment in the same project as source",
  "ActionType": "Octopus.Script",
  "Version": 1,
  "CommunityActionTemplateId": null,
  "Packages": [],
  "Properties": {
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptBody": "if (-not (Get-Module -Name EpiCloud -ListAvailable)) {\n    Install-Module EpiCloud -Scope CurrentUser -Force\n}\n\nConnect-EpiCloud -ClientKey $ClientKey -ClientSecret $ClientSecret -ProjectId $ProjectId\n\n$deployments = Get-EpiDeployment -ProjectId $ProjectId\n$ongoingdeployid = 0\nForeach ($deployment in $deployments) {\n    if ($deployment.parameters.targetEnvironment -eq $targetenvironment ) {\n    \tif ($deployment.status -eq 'AwaitingVerification' -Or $deployment.status -eq 'InProgress' ) {\n          $ongoingdeployid = $deployment.id\n          Write-Host $deployment\n          break\n      \t}\n    }\n}\n$startEpiDeploymentSplat = @{\n        ProjectId          = $ProjectId\n        SourceEnvironment  = $sourceenvironment\n        TargetEnvironment  = $targetenvironment\n        SourceApp = $apps\n        IncludeDb = [System.Convert]::ToBoolean($OctopusParameters['includedb'])\n        IncludeBlob = [System.Convert]::ToBoolean($OctopusParameters['includeblob'])\n        UseMaintenancePage = [System.Convert]::ToBoolean($OctopusParameters['maintenancepage'])\n}\n\nif ([System.Convert]::ToBoolean($OctopusParameters['smoothdeploy'])) {\n    $startEpiDeploymentSplat.Add('ZeroDowntimeMode', 'ReadOnly')\n}\n\nif ($ongoingdeployid -eq 0){\n    $deployment = Start-EpiDeployment @startEpiDeploymentSplat -Wait\n    if ($autocomplete -eq 'true') {\n        $deployment | Complete-EpiDeployment -Wait\n    }\n}\nelse {\n    Write-Warning \"There is already an ongoing deployment in progress ($ongoingdeployid). Complete or reset the ongoing deployment then try this again.\" \n}"
  },
  "Parameters": [
    {
      "Id": "caab69c6-35fa-48ad-b561-f49b79d49c9f",
      "Name": "ProjectId",
      "Label": "Project ID",
      "HelpText": "Your DXP project ID can easily be found in the API tab within the PaaS portal.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "d452e8a7-ddbb-484c-bbd7-353d276e3261",
      "Name": "ClientKey",
      "Label": "Client Key",
      "HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "081ad644-04bc-47a7-a241-df85b7aaca1b",
      "Name": "ClientSecret",
      "Label": "Client Secret",
      "HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "39194e0d-9f4f-428d-88a8-d2624d1f4908",
      "Name": "sourceenvironment",
      "Label": "Source Environment",
      "HelpText": "Select the environment which the code should be copied from",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Integration|Integration\nPreproduction|Preproduction\nProduction|Production"
      }
    },
    {
      "Id": "e61b1e60-eb96-41ef-8670-99713933014e",
      "Name": "targetenvironment",
      "Label": "Target Environment",
      "HelpText": "Select the environment that the package should be deployed to",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Integration|Integration\nPreproduction|Preproduction\nProduction|Production"
      }
    },
    {
      "Id": "1229ef3c-e191-400d-95c7-f950c8c92eff",
      "Name": "apps",
      "Label": "Applications to deploy",
      "HelpText": "Select what applications should be deployed.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "cms|CMS Only\ncommerce|Commerce Only\ncms,commerce|Both"
      }
    },
    {
      "Id": "ce603470-4c9b-4e88-99ca-384c5df95a3f",
      "Name": "maintenancepage",
      "Label": "Maintenance Page",
      "HelpText": "Select if you want this deployment to use a maintenance page.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "false|No\ntrue|Yes"
      }
    },
    {
      "Id": "b25e8b83-a7be-4ed2-b9fe-2098726e9bf7",
      "Name": "smoothdeploy",
      "Label": "Smooth Deploy",
      "HelpText": "Select if you want to use the Smooth Deploy process. The smooth deploy process will automatically use maintenance page.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "false|No\ntrue|Yes"
      }
    },
    {
      "Id": "6a7c3ef5-9345-4c23-815a-5dbd568e0bb3",
      "Name": "includeblob",
      "Label": "Include Blobs",
      "HelpText": "Include the source application(s) blobs in your deployment. The blobs on the target site will be overwritten.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "false|No\ntrue|Yes"
      }
    },
    {
      "Id": "85c2eff4-ae36-4bcc-91b2-55eaf768b4fe",
      "Name": "includedb",
      "Label": "Include DB",
      "HelpText": "Include the source application(s) database(s) in your deployment. The databases on the target site will be overwritten.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "false|No\ntrue|Yes"
      }
    },
    {
      "Id": "7761969b-453c-408c-881f-000f88d7ef55",
      "Name": "autocomplete",
      "Label": "Auto Complete",
      "HelpText": "Select if you want to auto complete the deployment instead of having to run another command to complete it after the first phase (not recommended for production deployments).",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "false|No\ntrue|Yes"
      }
    }
  ],
  "$Meta": {
    "ExportedAt": "2021-01-04T10:28:56.848Z",
    "OctopusVersion": "2020.4.11",
    "Type": "ActionTemplate"
  },
  "LastModifiedBy": "Your GitHub Username",
  "Category": "other"
}

Complete deployment

{
"Id": "f0e4c038-de0c-4478-99b4-527af20eb3de",
"Name": "Episerver DXP - Complete deploy",
"Description": "Deploy to a DXP environment using another environment in the same project as source",
"ActionType": "Octopus.Script",
"Version": 1,
"CommunityActionTemplateId": null,
"Packages": [],
"Properties": {
"Octopus.Action.Script.ScriptSource": "Inline",
"Octopus.Action.Script.Syntax": "PowerShell",
"Octopus.Action.Script.ScriptBody": "if (-not (Get-Module -Name EpiCloud -ListAvailable)) {\n Install-Module EpiCloud -Scope CurrentUser -Force\n}\n\nConnect-EpiCloud -ClientKey $ClientKey -ClientSecret $ClientSecret -ProjectId $ProjectId\n\n$deployments = Get-EpiDeployment -ProjectId $ProjectId\n$ongoingdeployid = 0\nForeach ($deployment in $deployments) {\n if ($deployment.parameters.targetEnvironment -eq $targetenvironment ) {\n \tif ($deployment.status -eq 'AwaitingVerification') {\n $ongoingdeployid = $deployment.id\n break\n \t}\n }\n}\n\nif ($ongoingdeployid -eq 0) {\n\tWrite-Error \"Unable to find a deployment with status 'AwaitingVerification', you can easily check in the PaaS portal web interface if you have any ongoing deployments.\"\n}\nelse {\n\tComplete-EpiDeployment -ProjectId $ProjectId -Id $ongoingdeployid -Wait\n}\n"
},
"Parameters": [
{
"Id": "caab69c6-35fa-48ad-b561-f49b79d49c9f",
"Name": "ProjectId",
"Label": "Project ID",
"HelpText": "Your DXP project ID can easily be found in the API tab within the PaaS portal.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "d452e8a7-ddbb-484c-bbd7-353d276e3261",
"Name": "ClientKey",
"Label": "Client Key",
"HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "081ad644-04bc-47a7-a241-df85b7aaca1b",
"Name": "ClientSecret",
"Label": "Client Secret",
"HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "Sensitive"
}
},
{
"Id": "e61b1e60-eb96-41ef-8670-99713933014e",
"Name": "targetenvironment",
"Label": "Target Environment",
"HelpText": "Select the environment that the package should be deployed to",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "Select",
"Octopus.SelectOptions": "Integration|Integration\nPreproduction|Preproduction\nProduction|Production"
}
}
],
"$Meta": {
"ExportedAt": "2020-12-20T12:17:02.612Z",
"OctopusVersion": "2020.4.11",
"Type": "ActionTemplate"
},
"LastModifiedBy": "Your GitHub Username",
"Category": "other"
}

To set up the first (start deploy) step, add it as a project process. Then all you have to do is specify the following properties:

  1. Executed on the Octopus Server.
  2. Project ID which you can find in the PaaS portal API tab.
  3. Your client key.
  4. Your client secret.
  5. What environment you want to deploy from.
  6. What environment you want to deploy to.
  7. What applications (CMS, Commerce or both) you want to deploy.
  8. Select if maintenance page should be used in the deployment.
  9. Select if you want to use the smooth deploy process.
  10. Select if you want to include blobs in your deployment.
  11. Select if you want to include databases in your deployment.
  12. Select if you want to auto complete the deployment.

The complete deploy step is pretty much the same process:

  1. Executed on the Octopus Server.
  2. Project ID which you can find in the PaaS portal API tab.
  3. Your client key.
  4. Your client secret.
  5. What environment you deployed to.

All that's left to do is to deploy. Setting the task log to Verbose gives you some progress details.

There are of course much more you can do such as customizing your lifecycle and environments.

Using project variables as values instead of the predefined ones that the template offers.

You can also build in your own checks and reset process steps between the phases. These templates are just the tip of the iceberg and a foundation to get you started.

Known limitations

  • When including database and blobs, the time it takes depends on the amount of blobs and size of database(s) that has to be copied. 
  • You can only run one deployment at a time within the same DXP project.
  • You can't use an empty environment as source environment (must contain code).
  • You can't include database and/or blobs to production more than once.

Content Synchronization between environments

Import the following step template into Library > Step Templates

{
  "Id": "ad099926-f0ae-47c8-8ce9-263cea491eb2",
  "Name": "Episerver DXP - Content Sync",
  "Description": "Allows you to content sync from production to lower environments",
  "ActionType": "Octopus.Script",
  "Version": 1,
  "CommunityActionTemplateId": null,
  "Packages": [],
  "Properties": {
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptBody": "if (-not (Get-Module -Name EpiCloud -ListAvailable)) {\n    Install-Module EpiCloud -Scope CurrentUser -Force\n}\n\nConnect-EpiCloud -ClientKey $ClientKey -ClientSecret $ClientSecret -ProjectId $ProjectId\n$deployments = Get-EpiDeployment -ProjectId $ProjectId\n$ongoingdeployid = 0\nForeach ($deployment in $deployments) {\n    if ($deployment.parameters.targetEnvironment -eq $targetenvironment ) {\n    \tif ($deployment.status -eq 'AwaitingVerification' -Or $deployment.status -eq 'InProgress' ) {\n          $ongoingdeployid = $deployment.id\n          Write-Host $deployment\n          break\n      \t}\n    }\n}\nWrite-Host \"You can see further progress by changing log level to Verbose\"\nif ($ongoingdeployid -eq 0) {\n\t$deployment = Start-EpiDeployment -ProjectId $ProjectId -SourceEnvironment $sourceenvironment -TargetEnvironment $targetenvironment -IncludeBlob -IncludeDb -Wait\n}\nelse {\n\tWrite-Warning \"There is already an ongoing deployment in progress ($ongoingdeployid). Complete or reset the ongoing deployment then try this again.\" \n}"
  },
  "Parameters": [
    {
      "Id": "22d4e3d6-0beb-4418-9728-6310919f7399",
      "Name": "ProjectId",
      "Label": "Project ID",
      "HelpText": "Your DXP project ID can easily be found in the API tab within the PaaS portal.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "be323764-e188-408b-b976-99d430872bbc",
      "Name": "ClientKey",
      "Label": "Client Key",
      "HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "66abd295-3e03-4868-94b1-7c19e793b999",
      "Name": "ClientSecret",
      "Label": "Client Secret",
      "HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "ba515efc-b780-40f7-8bd7-101052cccb42",
      "Name": "sourceenvironment",
      "Label": "Source Environment",
      "HelpText": "Select the environment that has the database and blobs you want to synchronize from.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Integration|Integration\nPreproduction|Preproduction\nProduction|Production"
      }
    },
    {
      "Id": "38449672-477e-48e3-aa99-efada447fad9",
      "Name": "targetenvironment",
      "Label": "Target Environment",
      "HelpText": "Select the environment that has the database and blobs you want to synchronize from.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Integration|Integration\nPreproduction|Preproduction"
      }
    }
  ],
  "$Meta": {
    "ExportedAt": "2020-12-28T15:38:02.708Z",
    "OctopusVersion": "2020.4.11",
    "Type": "ActionTemplate"
  },
  "LastModifiedBy": "Your GitHub Username",
  "Category": "other"
}

You can then use it in a project process where all you have to do is specify the following properties:

  1. Executed on the Octopus Server.
  2. Project ID which you can find in the PaaS portal API tab.
  3. Your client key.
  4. Your client secret.
  5. What environment you want to copy database and blobs from.
  6. What environment you want to copy database and blobs to.

Once you run this step it will start the content synchronization between the selected environments.

The content synchronization process counts as a deployment. This process will restart the app service on the target environment.

Known limitations

  • The time it takes depends on the amount of blobs and size of database(s) that has to be copied. 
  • You can only run one deployment at a time within the same DXP project.
  • You can't synchronize content to production, only from.

Exporting a DXP-hosted database to .bacpac file

Import the following step template into Library > Step Templates

{
  "Id": "054ebb26-4b9d-4636-a6e1-18d50afaad6c",
  "Name": "Episerver DXP - Export Database",
  "Description": "Description\nExports a database hosted in your DXP project as a .bacpac file which you can download and restore on a local environment.",
  "ActionType": "Octopus.Script",
  "Version": 1,
  "CommunityActionTemplateId": null,
  "Packages": [],
  "Properties": {
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptBody": "if (-not (Get-Module -Name EpiCloud -ListAvailable)) {\n    Install-Module EpiCloud -Scope CurrentUser -Force\n}\n\nConnect-EpiCloud -ClientKey $ClientKey -ClientSecret $ClientSecret -ProjectId $ProjectId\n\n$startEpiExportmentSplat = @{\n    ProjectId = \"$ProjectId\"\n    Wait = $false\n    Environment = \"$sourceenvironment\"\n    DatabaseName = \"$databasename\"\n    ClientSecret = \"$ClientSecret\"\n    ClientKey = \"$ClientKey\"\n}\n \nWrite-Host \"Starting the Export. Environment: $sourceenvironment | DB Name: $databasename\"\n \n$export = Start-EpiDatabaseExport -RetentionHours $retentionhours @startEpiExportmentSplat\n\n$exportId = $export | Select -ExpandProperty \"id\"\n \n$getEpiExportSplat = @{\n    ProjectId = \"$ProjectID\"\n    ClientSecret = \"$ClientSecret\"\n    ClientKey = \"$ClientKey\"\n    Id = \"$exportId\"\n    Environment = \"$sourceenvironment\"\n    DatabaseName = \"$databasename\"\n}\n \n$timesRun = 0\n$currExport = Get-EpiDatabaseExport @getEpiExportSplat | Select-Object -First 1\n$status = $currExport | Select -ExpandProperty \"status\"\n$exit = 0\n\nwhile($exit -ne 1){\n \n$currExport = Get-EpiDatabaseExport @getEpiExportSplat | Select-Object -First 1\n \n$status = $currExport | Select -ExpandProperty \"status\"\n \nWrite-Host \"Exporting In Progress. Elapsed time - \"$timesRun\":00\"\n \nif($status -ne 'InProgress'){\n    $exit = 1\n}\n$timesRun = $timesRun + 1;\n \nstart-sleep -Milliseconds 60000\n \n}\n\nif($status -eq \"Failed\"){\n    throw \"Export Failed.\"\n}\n \n$downloadLink = $currExport | Select -ExpandProperty \"downloadLink\"\n \nWrite-Host \"Export Finished. The download URL is available for $retentionhours hours:`n $downloadLink `n`n\""
  },
  "Parameters": [
    {
      "Id": "22d4e3d6-0beb-4418-9728-6310919f7399",
      "Name": "ProjectId",
      "Label": "Project ID",
      "HelpText": "Your DXP project ID can easily be found in the API tab within the PaaS portal.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "be323764-e188-408b-b976-99d430872bbc",
      "Name": "ClientKey",
      "Label": "Client Key",
      "HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "66abd295-3e03-4868-94b1-7c19e793b999",
      "Name": "ClientSecret",
      "Label": "Client Secret",
      "HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "ba515efc-b780-40f7-8bd7-101052cccb42",
      "Name": "sourceenvironment",
      "Label": "Source Environment",
      "HelpText": "Select the environment that has the database and blobs you want to synchronize from.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Integration|Integration\nPreproduction|Preproduction\nProduction|Production"
      }
    },
    {
      "Id": "cb458a22-6232-4b1c-8151-08808c30cc84",
      "Name": "databasename",
      "Label": "Database",
      "HelpText": "Select the database you want to export. Commerce is only available for commerce projects.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "epicms|CMS (epicms)\nepicommerce|Commerce (epicommerce)"
      }
    },
    {
      "Id": "5b56305d-0d8b-4ec6-af41-5b65ea3db1d3",
      "Name": "retentionhours",
      "Label": "Retention Hours",
      "HelpText": "Specify how many hours the download link should be available for.\nMinimum is 1 hour. Maximum is 168 hours.",
      "DefaultValue": "24",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    }
  ],
  "$Meta": {
    "ExportedAt": "2020-12-19T12:31:30.222Z",
    "OctopusVersion": "2020.4.11",
    "Type": "ActionTemplate"
  },
  "LastModifiedBy": "Your GitHub Username",
  "Category": "other"
}

You can then use it in a project process where all you have to do is specify the following properties:

  1. Executed on the Octopus Server.
  2. Project ID which you can find in the PaaS portal API tab.
  3. Your client key.
  4. Your client secret.
  5. What environment the database is hosted on.
  6. What database to export, CMS or Commerce. If you do not have a commerce project in DXP then you will not have a commerce database.
  7. How many hours the download link should be available.

Once you run this step it will export the database of your choice to a .bacpac file and return a URL where you can download it from.

Known limitations

  • The Export Database process can take a long time from a few hours and up to four days (in extreme cases). 
  • You can export only one database at a time within the same DXP project.

Exporting BLOBs to local path

Import the following step template into Library > Step Templates

{
  "Id": "a9daca16-6427-4246-9067-4b2f22863b06",
  "Name": "Episerver DXP - Export Blobs",
  "Description": "Exports a blobs hosted in your DXP project to a local path.",
  "ActionType": "Octopus.Script",
  "Version": 1,
  "CommunityActionTemplateId": null,
  "Packages": [],
  "Properties": {
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptBody": "function ImportAzureStorageModule {\n    $azModuleLoaded = Get-Module -Name \"Az.Storage\"\n\n    if (-not ($azureModuleLoaded -or $azModuleLoaded)) {\n        try {\n            $null = Import-Module -Name \"Az.Storage\" -ErrorAction Stop\n            $azModuleLoaded = $true\n        }\n        catch {\n            Write-Verbose \"Tried to find 'Az.Storage', module couldn't be imported.\"\n        }\n    }\n\n    if ($azModuleLoaded) {\n        \"Az\"\n    }\n    else {\n        throw \"'Az.Storage' module is required to run this cmdlet.\"\n    }\n}\n\nfunction AddTlsSecurityProtocolSupport {\n    <#\n    .SYNOPSIS\n    This helper function adds support for TLS protocol 1.1 and/or TLS 1.2\n    .DESCRIPTION\n    This helper function adds support for TLS protocol 1.1 and/or TLS 1.2\n    #>\n\n    [CmdletBinding()]\n    param(\n        [Parameter(Mandatory=$false)]\n        [Bool] $EnableTls11 = $true,\n        [Parameter(Mandatory=$false)]\n        [Bool] $EnableTls12 = $true\n    )\n\n    # Add support for TLS 1.1 and TLS 1.2\n    if (-not [Net.ServicePointManager]::SecurityProtocol.HasFlag([Net.SecurityProtocolType]::Tls11) -AND $EnableTls11) {\n        [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls11\n    }\n\n    if (-not [Net.ServicePointManager]::SecurityProtocol.HasFlag([Net.SecurityProtocolType]::Tls12) -AND $EnableTls12) {\n        [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12\n    }\n}\n\nfunction Join-Parts {\n    param\n    (\n        $Parts = $null,\n        $Separator = ''\n    )\n\n    ($Parts | Where-Object { $_ } | ForEach-Object { ([string]$_).trim($Separator) } | Where-Object { $_ } ) -join $Separator \n}\n  ####################################################################################\n  \n  If(!(test-path $downloaddestination))\n{\n      New-Item -ItemType Directory -Force -Path $downloaddestination\n}\n  \n  ####################################################################################\n  \nif ($PSVersionTable.PSEdition -eq 'Desktop' -and (Get-Module -Name AzureRM -ListAvailable)) {\n        Write-Warning -Message ('Az module not installed. Having both the AzureRM and ' +\n            'Az modules installed at the same time is not supported.')\n    } else {\n\t$env:PSModulePath = \"{0};{1}\" -f $env:PSModulePath, \"C:\\AzPowerShell\\Modules\"\n        Install-Module -Name Az -AllowClobber -Scope CurrentUser -Force\n    }\nif (-not (Get-Module -Name EpiCloud -ListAvailable)) {\n    Install-Module EpiCloud -Scope CurrentUser -Force\n}\n\nConnect-EpiCloud -ClientKey $ClientKey -ClientSecret $ClientSecret -ProjectId $ProjectId\n\t\n  ####################################################################################\n  \n     $linkSplat = @{\n        ProjectId = $ProjectId\n        Environment = $sourceenvironment\n        StorageContainer = $container\n\t\tRetentionHours = $retentionHours\n    }\n\n    $linkResult = Get-EpiStorageContainerSasLink @linkSplat\n\n    foreach ($link in $linkResult){\n        if ($link.containerName -eq $container) {\n\n            Write-Host \"Sas link           : $($link.sasLink)\"\n\n            $fullSasLink = $link.sasLink\n            $fullSasLink -match \"https:\\/\\/(.*).blob.core\" | Out-Null\n            $storageAccountName = $Matches[1]\n            Write-Host \"StorageAccountName : $storageAccountName\"\n\n            $fullSasLink -match \"(\\?.*)\" | Out-Null\n            $sasToken = $Matches[0]\n            Write-Host \"SAS token          : $sasToken\"\n        } else {\n            Write-Host \"Ignore container   : $($link.containerName)\"\n        }\n    }\n    \n    if ($null -eq $sasToken -or $sasToken.Length -eq 0) {\n        Write-Warning \"Did not found container $container in the list. Look in the log and see if your blob container have another name then mysitemedia. If so, specify that name as param -container. Example: Ignore container: projectname-assets. Then set -container 'projectname-assets'\"\n        exit\n    }\n\n\nAddTlsSecurityProtocolSupport\n\nImportAzureStorageModule\n\n$ctx = New-AzStorageContext -StorageAccountName $storageAccountName -SASToken $sasToken -ErrorAction Stop\n\nif ($null -eq $ctx){\n    Write-Error \"No context. The provided SASToken is not valid.\"\n    exit\n}\nelse {\n   $blobContents = Get-AzStorageBlob -Container $container  -Context $ctx | Sort-Object -Property LastModified -Descending\n\n    Write-Host \"Found $($blobContents.Length) BlobContent.\"\n\n    if ($blobContents.Length -eq 0) {\n        Write-Warning \"No blob/files found in the container '$container'\"\n        exit\n    }\n\n    if ($maxfiles -eq 0) {\n        $maxfiles = [int]$blobContents.Length\n    }\n    $downloadedFiles = 0\n    Write-Host \"---------------------------------------------------\"\n    foreach($blobContent in $blobContents)  \n    {  \n        if ($downloadedFiles -ge $maxfiles){\n            Write-Host \"Hit max files to download ($maxfiles)\"\n            break\n        }\n\n       $filePath = Join-Parts -Separator '\\' -Parts $downloaddestination, $blobContent.Name\n       $fileExist = Test-Path $downloaddestination -PathType Leaf\n\n       if ($fileExist -eq $false){\n            ## Download the blob content \n            Write-Host \"Download #$($downloadedFiles + 1) - $($blobContent.Name) $(if ($fileExist -eq $true) {\"overwrite\"} else {\"to\"}) $filePath\" \n            Get-AzStorageBlobContent -Container $container  -Context $ctx -Blob $blobContent.Name -Destination $downloaddestination -Force  \n            $downloadedFiles++\n       }\n       else\n       {\n            Write-Host \"File exist on disc: $filePath.\" \n       }\n\n        $procentage = [int](($downloadedFiles / $maxfiles) * 100)\n        Write-Progress -Activity \"Download files\" -Status \"$procentage% Complete:\" -PercentComplete $procentage;\n    }\n    Write-Host \"---------------------------------------------------\"\n}"
  },
  "Parameters": [
    {
      "Id": "22d4e3d6-0beb-4418-9728-6310919f7399",
      "Name": "ProjectId",
      "Label": "Project ID",
      "HelpText": "Your DXP project ID can easily be found in the API tab within the PaaS portal.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "be323764-e188-408b-b976-99d430872bbc",
      "Name": "ClientKey",
      "Label": "Client Key",
      "HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "66abd295-3e03-4868-94b1-7c19e793b999",
      "Name": "ClientSecret",
      "Label": "Client Secret",
      "HelpText": "Your Client/API Key can be generated in the API tab within the PaaS portal.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "ba515efc-b780-40f7-8bd7-101052cccb42",
      "Name": "sourceenvironment",
      "Label": "Source Environment",
      "HelpText": "Select the environment that has the database and blobs you want to synchronize from.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Integration|Integration\nPreproduction|Preproduction\nProduction|Production"
      }
    },
    {
      "Id": "cb458a22-6232-4b1c-8151-08808c30cc84",
      "Name": "container",
      "Label": "Storage Container",
      "HelpText": "Name of the storage container you want to download blobs from.",
      "DefaultValue": "mysitemedia",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText",
        "Octopus.SelectOptions": "epicms|CMS (epicms)\nepicommerce|Commerce (epicommerce)"
      }
    },
    {
      "Id": "1996bf35-de57-4a5e-9147-20b4445e0998",
      "Name": "downloaddestination",
      "Label": "Destination Folder",
      "HelpText": "Path where the files should be downloaded to, for example C:\\temp",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "9a29b2c0-025a-41f0-8433-b5559436309d",
      "Name": "maxfiles",
      "Label": "Maximum amount of files to download",
      "HelpText": "Specify how many files you want to download. Use 0 for all.",
      "DefaultValue": "0",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "5b56305d-0d8b-4ec6-af41-5b65ea3db1d3",
      "Name": "retentionHours",
      "Label": "Retention Hours",
      "HelpText": "Specify how many hours the download link should be available for.\nDefault is 24 hours.",
      "DefaultValue": "24",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    }
  ],
  "$Meta": {
    "ExportedAt": "2020-12-20T14:23:16.161Z",
    "OctopusVersion": "2020.4.11",
    "Type": "ActionTemplate"
  },
  "LastModifiedBy": "Your GitHub Username",
  "Category": "other"
}

You can then use it in a project process where all you have to do is specify the following properties:

  1. Executed on the Octopus Server.
  2. Project ID which you can find in the PaaS portal API tab.
  3. Your client key.
  4. Your client secret.
  5. What environment the blobs are hosted on.
  6. The container name.
  7. Destination folder where your blobs should be downloaded to.
  8. Amount of files it should download (0 to download all).
  9. How many hours the generated download link used to download the blobs should be available.

Once you run this step it will export (download) the blobs within the specified container to the local path you've specified.

This can also be used to download web and application logs.

Known limitations

  • The Export Blobs process time completely depends on the amount of blobs you're downloading. As well as your Octopus Server's network speed. 
  • You can export only one container at a time within the same DXP project.

 References used when creating this article:

Jan 04, 2021

Comments

Johan Book
Johan Book Feb 1, 2021 10:07 PM

This is a very nice writeup! Thanks Andreas!

Please login to comment.
Latest blogs
Optimizely Forms: You cannot submit this form because an administrator has turned off data storage.

Do not let this error message scare you, the solution is quite simple!

Tomas Hensrud Gulla | Oct 4, 2024 | Syndicated blog

Add your own tools to the Optimizely CMS 12 admin menu

The menus in Optimizely CMS can be extended using a MenuProvider, and using the path parameter you decide what menu you want to add additional menu...

Tomas Hensrud Gulla | Oct 3, 2024 | Syndicated blog

Integrating Optimizely DAM with Your Website

This article is the second in a series about integrating Optimizely DAM with websites. It discusses how to install the necessary package and code t...

Andrew Markham | Sep 28, 2024 | Syndicated blog

Opticon 2024 - highlights

I went to Opticon in Stockholm and here are my brief highlights based on the demos, presentations and roadmaps  Optimizely CMS SaaS will start to...

Daniel Ovaska | Sep 27, 2024