Public/New-PredeployRunbook.ps1


function New-PredeployRunbook {
    <#
.SYNOPSIS
    Creates or updates a predeploy runbook which can be used to transfer packages before deployment
.DESCRIPTION
    Long description
.EXAMPLE
    PS C:\> <example usage>
    Explanation of what the example does
.INPUTS
    Inputs (if any)
.OUTPUTS
    Output (if any)
.NOTES
    General notes
#>

    [CmdletBinding(DefaultParameterSetName = 'Parameter Set 1',
        SupportsShouldProcess = $true,
        PositionalBinding = $false,
        HelpUri = 'http://www.microsoft.com/',
        ConfirmImpact = 'Medium')]
    [Alias("Update-PredeployRunbook")]
    #[OutputType([String])]

    Param (
        # octopus project the runbook is for
        [Parameter(mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ParameterSetName = 'Project')]
        [ValidateNotNullOrEmpty()]
        [Octopus.Client.Model.ReleaseResource[]]
        $Release,
        # Name of the runbook.
        [Parameter(Mandatory = $false,
            Position = 1,
            HelpMessage = "Optional Name of the Runbook")]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [String]$Name,
        [Parameter(Mandatory = $false,
            Position = 2,
            HelpMessage = "If set the Runbook Snapshot will be directly published")]
        [Switch]$Publish
    )

    begin {}

    process {

        $project = Get-Project -ID $release.ProjectId
        $channel = Get-Channel -ID $release.ChannelId
        # get release process snapshot for the release
        $releaseprocesses = $repo._repository.DeploymentProcesses.get($release.ProjectDeploymentProcessSnapshotId)


        # get each step of deployment process
        $steps = $releaseprocesses.Steps
        $stepsPackagesRoles = [System.Collections.ArrayList]::new()
        # iterate through each step and extract all actions that are relevant for the deployment
        foreach ($step in $steps) {
            Write-Verbose "Analyzing Step `"$($step.name)`""
            if ($step.Actions.packages) {
                Write-Verbose "Step `"$($step.name)`" contains `"$($step.Actions.packages.count)`" packages"
                foreach ($action in $step.Actions) {
                    if ($action.packages.Count -gt 0) {
                        Write-Verbose "Action `"$($step.name)/$($action.name)`" contains `"$($action.packages.Count)`" packaged"
                        if (($action.channels -contains $release.ChannelId -or $action.channels.count -eq 0) -And $action.IsDisabled -ne $true ) {
                            Write-Verbose "Action `"$($step.name)/$($action.name)`" is enabled and is part of deployment channel `"$($channel.name)`""
                            $null = $stepsPackagesRoles.add([PSCustomObject]@{
                                    StepName             = $step.Name
                                    ActionName           = $action.name
                                    TargetRoles          = $step.properties["Octopus.Action.TargetRoles"]
                                    Packages             = @($action.packages)
                                    TenantTags           = $action.TenantTags
                                    Environments         = $action.Environments
                                    ExcludedEnvironments = $action.ExcludedEnvironments
                                    ActionType           = $action.ActionType
                                })
                        } else {
                            Write-Verbose "Action `"$($step.name)/$($action.name)`" is disabled or doesn't belong to the same channel"
                        }
                    } else {
                        Write-Verbose "Action `"$($step.name)/$($action.name)`" doesn't contain any packages"
                    }
                }

            }

        }
        if (!($stepsPackagesRoles)) {
            Throw "`"$($project.name)`" - `"$($release.version)`" doesn't have any packages"
        }
        if (-not $name) {
            #set default name
            $name = "Predeploy - " + (Get-Channel -ID $release.ChannelId).name
        }
        if ($pscmdlet.ShouldProcess($repo._endpoint.OctopusServer.ToString(), "Create a predeploy runbook for the project $($project.name) release $($release.Version)")) {
            # create or modify the
            Write-Verbose "Creating or updating runbook `"$name`""
            $runbookEditor = $repo._repository.Runbooks.CreateOrModify($project , $name, "This runbook pre-deploys packages for this project")
            $runbookEditor.Instance.MultiTenancyMode = $project.TenantedDeploymentMode
            $runbookEditor.Instance.ConnectivityPolicy.AllowDeploymentsToNoTargets = $project.ProjectConnectivityPolicy.AllowDeploymentsToNoTargets
            $runbookEditor.Instance.ConnectivityPolicy.ExcludeUnhealthyTargets = $project.ProjectConnectivityPolicy.ExcludeUnhealthyTargets
            #update runbook
            $repo._repository.Runbooks.Modify($runbookEditor.Instance) | Out-Null

            #remove all existing steps from runbook
            $runbookprocess = $runbookEditor.RunbookProcess.Instance
            $runbookprocess.ClearSteps() | Out-Null
            #$runbookprocess = $repo.RunbookProcesses.Modify($runbookprocess)

            # Create a package deploy step for each package
            # This is only a reference to a package but not to a specific version.
            foreach ($_stepsPackagesRoles in $stepsPackagesRoles) {
                if ($_stepsPackagesRoles.Packages) {
                    $Stepname = $_stepsPackagesRoles.StepName
                    $role = $_stepsPackagesRoles.TargetRoles
                    $step = $runbookprocess.AddOrUpdateStep($Stepname)
                    $step.Condition = [Octopus.Client.Model.DeploymentStepCondition]::Success # Step run condition (Success = Only run if previous step succeeds)
                    if (! ($step.Properties."Octopus.Action.TargetRoles")) {
                        $step.Properties.Add("Octopus.Action.TargetRoles", $role)
                    }
                    foreach ($package in $_stepsPackagesRoles.Packages ) {
                        $scriptAction = [Octopus.Client.Model.DeploymentActionResource]::new() # Create the steps action type
                        $scriptAction.ActionType = "Octopus.TentaclePackage" # This will define this as a Script step "Octopus.Script", "Octopus.TentaclePackage"

                        # depending on ActionType the identifier to find the right version is PackageReferenceName (e.g. script step) or actionname (e.g. deploy step)
                        # Octopus.TentaclePackage step only contains one packages and the release.SelectedPackages has no PackageReferenceName. In this case the package is not identified by the the package identifier in PackageReferenceName but by the action name and therefor has to be identical to the one in the release
                        if ($_stepsPackagesRoles.actionType -eq 'Octopus.TentaclePackage') {
                            $scriptAction.Name = ($_stepsPackagesRoles.ActionName)
                        } else {
                            $scriptAction.Name = ($_stepsPackagesRoles.ActionName + "_" + $package.PackageId)
                        }
                        $pack = [Octopus.Client.Model.PackageReference]::new()
                        $pack = $package
                        $pack.name = $null
                        $scriptAction.Packages.Add($pack)
                        $step.Actions.Add($scriptAction)
                    }
                    $runbookprocess = $repo._repository.RunbookProcesses.Modify($runbookprocess)
                }

            }
            #create release publish

            # create snapshot resource
            $runbookSnapshot = [Octopus.Client.Model.RunbookSnapshotResource]::new()

            $runbookSnapshot.RunbookId = $RunbookEditor.Instance.id
            $runbookSnapshot.FrozenRunbookProcessId = $runbookprocess.Id
            $runbookSnapshot.SpaceId = $runbookprocess.SpaceId
            $runbookSnapshot.ProjectId = $runbookprocess.ProjectId

            #create short random snapshot name
            $shortRandom = ([System.IO.Path]::GetRandomFileName()).Split(".")[0]
            $runbookSnapshot.Name = $release.version + " - " + $shortRandom

            # add package version to each step to snapshot
            foreach ($rbStep in $runbookprocess.Steps) {
                foreach ($rbActions in $rbStep.Actions) {
                    foreach ($rbPackage in $rbActions.packages) {
                        $selectedPackages = [Octopus.Client.Model.SelectedPackage]::new()
                        $selectedPackages.StepName = $rbStep.Name
                        $selectedPackages.ActionName = $rbActions.Name

                        # depending on ActionType the identifier is PackageReferenceName (e.g. script step) or the action name must be the same (e.g. deploy step)
                        $selectedPackages.Version = ($release.SelectedPackages | Where-Object PackageReferenceName -EQ $rbPackage.PackageId).Version
                        if (!($selectedPackages.Version)) {
                            $selectedPackages.Version = ($release.SelectedPackages | Where-Object ActionName -EQ $rbActions.Name).Version
                        }
                        $runbookSnapshot.SelectedPackages.Add($selectedPackages)
                    }
                }
            }

            # create snapshot
            $runbookSnapshot = $repo._repository.RunbookSnapshots.Create($runbookSnapshot)

            # publish the new runbook snapshot
            if ($Publish.IsPresent) {
                Write-Verbose "Publishing runbook `"$name`" as `"$($runbookSnapshot.Name)`" in project `"$($project.name)`""
                $runbookEditor.Instance.PublishedRunbookSnapshotId = $runbookSnapshot.id
                $null = $repo._repository.Runbooks.Modify($RunbookEditor.instance)
            }
        }
    }

    end {}
}