Public/Tenant/New-VergeTenantClone.ps1

function New-VergeTenantClone {
    <#
    .SYNOPSIS
        Creates a clone of an existing VergeOS tenant.

    .DESCRIPTION
        New-VergeTenantClone creates a new tenant that is a copy of an existing tenant.
        The clone includes all configuration, VMs, networks, and storage by default.
        You can optionally exclude certain components from the clone.

    .PARAMETER SourceTenant
        A tenant object from Get-VergeTenant to clone. Accepts pipeline input.

    .PARAMETER SourceName
        The name of the tenant to clone.

    .PARAMETER SourceKey
        The unique key (ID) of the tenant to clone.

    .PARAMETER Name
        The name for the new cloned tenant. If not specified, a default name
        like "Clone of SourceName XXXXX" will be generated.

    .PARAMETER NoNetwork
        Do not clone the network configuration.

    .PARAMETER NoStorage
        Do not clone the storage configuration.

    .PARAMETER NoNodes
        Do not clone the nodes (VMs).

    .PARAMETER PassThru
        Return the cloned tenant object after creation.

    .PARAMETER Server
        The VergeOS connection to use. Defaults to the current default connection.

    .EXAMPLE
        New-VergeTenantClone -SourceName "Customer01" -Name "Customer01-Test"

        Creates a clone of "Customer01" named "Customer01-Test".

    .EXAMPLE
        Get-VergeTenant -Name "Template" | New-VergeTenantClone -Name "NewCustomer"

        Clones the "Template" tenant to create "NewCustomer".

    .EXAMPLE
        New-VergeTenantClone -SourceName "Customer01" -Name "NetworkTest" -NoStorage -NoNodes

        Creates a clone with only the network configuration (no storage or VMs).

    .EXAMPLE
        New-VergeTenantClone -SourceName "Customer01" -PassThru

        Creates a clone with default naming and returns the tenant object.

    .OUTPUTS
        None by default. Verge.Tenant when -PassThru is specified.

    .NOTES
        Cloning can take time depending on the size of the tenant.
        The source tenant does not need to be powered off for cloning.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'BySourceName')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'BySourceTenant')]
        [PSTypeName('Verge.Tenant')]
        [PSCustomObject]$SourceTenant,

        [Parameter(Mandatory, Position = 0, ParameterSetName = 'BySourceName')]
        [string]$SourceName,

        [Parameter(Mandatory, ParameterSetName = 'BySourceKey')]
        [int]$SourceKey,

        [Parameter(Position = 1)]
        [ValidateLength(1, 120)]
        [string]$Name,

        [Parameter()]
        [switch]$NoNetwork,

        [Parameter()]
        [switch]$NoStorage,

        [Parameter()]
        [switch]$NoNodes,

        [Parameter()]
        [switch]$PassThru,

        [Parameter()]
        [object]$Server
    )

    begin {
        # Resolve connection
        if (-not $Server) {
            $Server = $script:DefaultConnection
        }
        if (-not $Server) {
            throw [System.InvalidOperationException]::new(
                'Not connected to VergeOS. Use Connect-VergeOS to establish a connection.'
            )
        }
    }

    process {
        # Resolve source tenant based on parameter set
        $sourceTarget = switch ($PSCmdlet.ParameterSetName) {
            'BySourceName' {
                Get-VergeTenant -Name $SourceName -Server $Server
            }
            'BySourceKey' {
                Get-VergeTenant -Key $SourceKey -Server $Server
            }
            'BySourceTenant' {
                $SourceTenant
            }
        }

        foreach ($source in $sourceTarget) {
            if (-not $source) {
                continue
            }

            # Check if source tenant is a snapshot
            if ($source.IsSnapshot) {
                Write-Error -Message "Cannot clone tenant '$($source.Name)': Source is a snapshot. Use tenant restore instead." -ErrorId 'CannotCloneSnapshot'
                continue
            }

            # Determine clone name
            $cloneName = if ($Name) { $Name } else { "Clone of $($source.Name) $(Get-Random -Maximum 99999)" }

            # Build action body
            $body = @{
                tenant = $source.Key
                action = 'clone'
                params = @{
                    name = $cloneName
                }
            }

            # Add exclusion options
            if ($NoNetwork) {
                $body['params']['no_vnet'] = $true
            }

            if ($NoStorage) {
                $body['params']['no_storage'] = $true
            }

            if ($NoNodes) {
                $body['params']['no_nodes'] = $true
            }

            # Build description of what's being cloned
            $excludeList = @()
            if ($NoNetwork) { $excludeList += "network" }
            if ($NoStorage) { $excludeList += "storage" }
            if ($NoNodes) { $excludeList += "nodes" }
            $excludeDesc = if ($excludeList.Count -gt 0) { " (excluding: $($excludeList -join ', '))" } else { "" }

            # Confirm action
            if ($PSCmdlet.ShouldProcess("$($source.Name) -> $cloneName", "Clone Tenant$excludeDesc")) {
                try {
                    Write-Verbose "Cloning tenant '$($source.Name)' to '$cloneName'"
                    $response = Invoke-VergeAPI -Method POST -Endpoint 'tenant_actions' -Body $body -Connection $Server

                    Write-Verbose "Clone operation initiated for tenant '$($source.Name)'"

                    # Try to get the task to find the new tenant key
                    $taskKey = $response.'$key'
                    if ($taskKey) {
                        Write-Verbose "Clone task started with key: $taskKey"
                    }

                    if ($PassThru) {
                        # Wait a moment for the clone to be created, then find it
                        Start-Sleep -Seconds 2

                        # Find the new tenant by name
                        $newTenant = Get-VergeTenant -Name $cloneName -Server $Server
                        if ($newTenant) {
                            Write-Output $newTenant
                        }
                        else {
                            Write-Warning "Clone operation initiated but tenant '$cloneName' not yet found. It may still be creating."
                        }
                    }
                }
                catch {
                    $errorMessage = $_.Exception.Message
                    if ($errorMessage -match 'already in use') {
                        throw "A tenant with the name '$cloneName' already exists."
                    }
                    Write-Error -Message "Failed to clone tenant '$($source.Name)': $errorMessage" -ErrorId 'TenantCloneFailed'
                }
            }
        }
    }
}