OctopusEnviroment.psm1

Import-Module PowershellOctopusClientPS
(Get-Module -ListAvailable PowershellOctopusClientPS).ModuleBase

try
{
    $octoclientpath = Join-Path -Path (Get-Module -ListAvailable PowershellOctopusClientPS).ModuleBase -ChildPath "lib\Octopus.Client.dll"
    newtonsoftPath = Join-Path -Path (Get-Module -ListAvailable PowershellOctopusClientPS).ModuleBase -ChildPath "lib\Newtonsoft.Json.dll"
    Add-Type -Path $newtonsoftPath
    Add-Type -Path $octoclientpath
}
catch [System.Reflection.ReflectionTypeLoadException]
{    #system.attonations always throws
    #Write-Host "Message: $($_.Exception.Message)"
    #Write-Host "StackTrace: $($_.Exception.StackTrace)"
    #Write-Host "LoaderExceptions: $($_.Exception.LoaderExceptions)"
}
<#
    .SYNOPSIS
    return a setup client
#>

function LoadOctopusClientAndDependancies{
[OutputType([Octopus.Client.OctopusClient])]
Param(
        [parameter(Mandatory=$true)][String]$url,
        [parameter(Mandatory=$true)][String]$apiKey
    )
    
    $client = [Octopus.Client.OctopusClient]::new([Octopus.Client.OctopusServerEndpoint]::new($url, $apiKey))
    $client
}

<#
    .SYNOPSIS
    Deletes an enviroment, project links are removed automatically
#>

function DeleteOctopusEnviroment {
Param(
        [parameter(Mandatory=$true)] [String]$octopusURL,
        [parameter(Mandatory=$true)] [String]$octopusAPIKey,
        #Name of the enviroment to destroy.
        [parameter(Mandatory=$true)] [String]$enviromentName,
        #Octopus space to destroy enviroment in, if empty/null will add to all spaces.
        [parameter(Mandatory=$false)][String]$inSpace = ''
    )

    $client = LoadOctopusClientAndDependancies -url $octopusURL -apiKey $octopusAPIKey   
    $repository = $client.ForSystem()
    $spaces = $null

    if([string]::IsNullOrEmpty($inSpace)) {
        write-output "Space was empty removing $enviromentName from ALL spaces."
        $spaces = $repository.Spaces.GetAll()        
    }else{
        $spaces = $repository.Spaces.FindByName($inSpace)        
    }
    $spaces | foreach { 
        try {
            $currSpace = $repository.Spaces.FindByName($_.Name)
            $repositoryForSpace = $client.ForSpace($currSpace)
            $theEnviromentResource = $repositoryForSpace.Environments.FindByName($enviromentName)
            if(-not($theEnviromentResource -eq $null)) {
                $lCycles = $repositoryForSpace.Lifecycles.FindAll()
                if(-not($lCycles -eq $null)){
                    $lCycles | foreach{
                        $foundPhase = $_.Phases.FindIndex({$args[0].Name -eq $enviromentName})
                        if($foundPhase -gt -1){
                            $_.Phases.RemoveAt($foundPhase) | out-null
                            if($_.Phases.Count -eq 0){ 
                                write-output "Deleting Lifecycle: $($_.Name)" 
                                $repositoryForSpace.Lifecycles.Delete($_)                           
                            }
                            else { 
                                write-output "Deleting Phase: $enviromentName from Lifecycle: $($_.Name)" 
                                $repositoryForSpace.Lifecycles.Modify($_) | out-null
                            }                        
                        }                    
                    }
                }
                write-output "Deleting Enviroment: $($theEnviromentResource.Name)."
                $repositoryForSpace.Environments.Delete($theEnviromentResource)
            }
        }catch {
            Write-Error "Error in DeleteOctopusEnviroment"
        }
    }
}

<#
    .SYNOPSIS
    Creates a new enviroment in octopus and optionally creates a lifecycle for it and adds it to existing default lifecycle
#>

function CreateOctopusEnviroment {
Param(
        [parameter(Mandatory=$true)][String] $octopusURL,
        [parameter(Mandatory=$true)][String] $octopusAPIKey,
        #Name of the enviroment to create.
        [parameter(Mandatory=$true)][String] $enviromentName,
        #Octopus space to add enviroment to, if empty/null will add to all spaces.
        [parameter(Mandatory=$false)][String]$inSpace = '',
        #Should a lifecycle be created that only contains this enviroment?
        [parameter(Mandatory=$false)][bool]  $createStandaloneLifecycle = $false,
        #if a value is provided a stage for this enviroment will be created and inserted into the default lifecyle before the stage provided.
        [parameter(Mandatory=$false)][String]$addToDefaultLifecycleBeforeStage = '',        
        [parameter(Mandatory=$false)][Bool]  $createWorkerPool = $true,
        [paramater(Mandatory=$false)][Bool]  $createCloudRegion = $false
    )

    $client = LoadOctopusClientAndDependancies -url $octopusURL -apiKey $octopusAPIKey       
    $repository = $client.ForSystem()
    $spaces = $null

    if([string]::IsNullOrEmpty($inSpace) -eq $true) {
        write-output "Space was empty adding Enviroment: $enviromentName to ALL spaces."
        $spaces = $repository.Spaces.GetAll()        
    }else{
        $spaces = $repository.Spaces.FindByName($inSpace)        
    }
    $spaces | foreach { 
        $currSpace = $repository.Spaces.FindByName($_.Name)
        $repositoryForSpace = $client.ForSpace($currSpace)
        $createdEnviroment = $repositoryForSpace.Environments.CreateOrModify($enviromentName)
        $newEnviromentAsResouce = $repositoryForSpace.Environments.FindByName($createdEnviroment.Instance.Name)            
        write-output "Added Enviroment $enviromentName to space $($currSpace.Name)"

        if($createStandaloneLifecycle -eq $true) {
            $newLifecycleResource = [Octopus.Client.Model.LifecycleResource]::new()
            $newLifecycleResource.Name = $enviromentName  
                              
            $releaseRetention = [Octopus.Client.Model.RetentionPeriod]::new(2,[Octopus.Client.Model.RetentionUnit]::Items)
            $newLifecycleResource.WithReleaseRetentionPolicy($releaseRetention).WithTentacleRetentionPolicy($releaseRetention) | Out-Null
            
            $standAlonePhase = $newLifecycleResource.AddOrUpdatePhase($enviromentName)
            $standAlonePhase.IsOptionalPhase = $false
            [Octopus.Client.Model.EnvironmentResource[]]$phaseEnviroments = @($newEnviromentAsResouce)
            $standAlonePhase.WithOptionalDeploymentTargets($phaseEnviroments) | Out-Null       
            $repositoryForSpace.Lifecycles.Create($newLifecycleResource) | Out-Null
            write-output "Added lifecycle $enviromentName to space $currSpace"
        }

        if(-not ([string]::IsNullOrEmpty($addToDefaultLifecycleBeforeStage))) {                                    
            $lifecycle = $repositoryForSpace.Lifecycles.FindByName("Default Lifecycle")           
            $phasesList = $lifecycle.Phases
            $newPhase = [Octopus.Client.Model.PhaseResource]::new()
            $newPhase.Name = $enviromentName
            $newPhase.IsOptionalPhase = $true
            $newPhase.WithOptionalDeploymentTargets($newEnviromentAsResouce) | Out-Null
            $phasesList.Insert($phasesList.FindIndex({$args[0].Name -eq $addToDefaultLifecycleBeforeStage}),$newPhase) | Out-Null        
            $repositoryForSpace.Lifecycles.Modify($lifecycle) | Out-Null
            write-output "Added phase $enviromentName to the Default Lifecycle in space $currSpace"
        }
        
    }
}

<#
    .SYNOPSIS
    Links an enviroment to projects and tenants
#>

function LinkProjectAndEnviromentAndTenant {
Param(
        [parameter(Mandatory=$true)][String]$octopusURL,
        [parameter(Mandatory=$true)][String]$octopusAPIKey,        
        #Name of the enviroment to linkup.
        [parameter(Mandatory=$true)][String]$enviromentName,
        #Octopus space to perform action in, if null will use all spaces.
        [parameter(Mandatory=$false)][String]$inSpace = '',
        #name of the project to link, if null will link all
        [parameter(Mandatory=$false)][String]$projectName = '',
        #name of the tenant to link, if null will link all
        [parameter(Mandatory=$false)][String]$tenantName = '',
         #tenant tags to lookup tenants to link, only used if tenantName is null
        [parameter(Mandatory=$false)][String]$tenantTag = ''
    )
    if( -not ([string]::IsNullOrEmpty($tenantTag))){
        if(-not([string]::IsNullOrEmpty($tenantName))){
            Throw 'Please provide only a tenant tag or tenant name paramater, both were provided. Exiting!'
        }
    }
    if([string]::IsNullOrEmpty($tenantTag)){
        if([string]::IsNullOrEmpty($tenantName)){
            Throw 'Please provide a tenant tag or tenant name. Exiting!'
        }
    }

    $client = LoadOctopusClientAndDependancies -url $octopusURL -apiKey $octopusAPIKey       
    $repository = $client.ForSystem()
    
    $spaces = $null
    $projects = $null
    $tenants = [System.Collections.Generic.List[Octopus.Client.Model.TenantResource]]::new()

    if([string]::IsNullOrEmpty($inSpace) -eq $true) {       
        $spaces = $repository.Spaces.GetAll()        
    }else{
        $spaces = $repository.Spaces.FindByName($inSpace)        
    }

    $spaces | foreach { 
        $currSpace = $repository.Spaces.FindByName($_.Name)
        $repositoryForSpace = $client.ForSpace($currSpace)
        $envResource = $repositoryForSpace.Environments.FindByName($enviromentName)

        if(-not($envResource -eq $null)){
            if([string]::IsNullOrEmpty($tenantName) -eq $true){                
                $tmpTenants = $repositoryForSpace.Tenants.GetAll()
                if([string]::IsNullOrEmpty($tenantTag) -eq $true){
                    $tenants = $tmpTenants
                }else {
                    $tmpTenants | foreach {
                        $currTmpTenant = $_
                        if($currTmpTenant.TenantTags.Contains($tenantTag) -eq $true) {
                            $tenants.Add($currTmpTenant)
                        }
                    }
                }
            }else {
                $tenants = $repositoryForSpace.Tenants.FindByName($tenantName)
            }
        
            if([string]::IsNullOrEmpty($projectName) -eq $true){
                $projects = $repositoryForSpace.Projects.GetAll()
            }else {
                $projects = $repositoryForSpace.Projects.FindByName($projectName)
            }

            $tenants | foreach {
                $currTenant = $_                
                $projects | foreach {
                    $currProject = $_
                    if(-not($currProject.TenantedDeploymentMode -eq "Untenanted")) {
                        if($currTenant.ProjectEnvironments.ContainsKey($currProject.Id) -eq $false){
                            write-output "Making a link from Tenant: $($currTenant.Name) to Project: $($currProject.Name) in Enviroment: $($envResource.Name)."
                            $item = $currTenant.ProjectEnvironments.Add($currProject.Id,$envResource.Id)
                        }else{
                            write-output "Adding Enviroment: $($envResource.Name) to existing $($currTenant.Name)/$($currProject.Name) link."
                            $tenantProj = $currTenant.ProjectEnvironments[$currProject.Id]
                            $tenantProj.Add($envResource.Id) | Out-Null                    
                        }                 
                        $repositoryForSpace.Tenants.Modify($currTenant) | Out-Null
                    }
                }                
            }
        }      
    }
}