PSDigitalOcean.psm1

#Region '.\Classes\1.class1.ps1' -1

class Class1
{
    [string]$Name = 'Class1'

    Class1()
    {
        #default Constructor
    }

    [String] ToString()
    {
        # Typo "calss" is intentional
        return ( 'This calss is {0}' -f $this.Name)
    }
}
#EndRegion '.\Classes\1.class1.ps1' 16
#Region '.\Classes\2.class2.ps1' -1

class Class2
{
    [string]$Name = 'Class2'

    Class2()
    {
        #default constructor
    }

    [String] ToString()
    {
        return ( 'This calss is {0}' -f $this.Name)
    }
}
#EndRegion '.\Classes\2.class2.ps1' 15
#Region '.\Classes\3.class11.ps1' -1

class Class11 : Class1
{
    [string]$Name = 'Class11'

    Class11 ()
    {
    }

    [String] ToString()
    {
        return ( 'This calss is {0}:{1}' -f $this.Name,'class1')
    }
}
#EndRegion '.\Classes\3.class11.ps1' 14
#Region '.\Classes\4.class12.ps1' -1

class Class12 : Class1
{
    [string]$Name = 'Class12'

    Class12 ()
    {
    }

    [String] ToString()
    {
        return ( 'This calss is {0}:{1}' -f $this.Name,'class1')
    }
}
#EndRegion '.\Classes\4.class12.ps1' 14
#Region '.\Classes\5.DigitalOceanImage.ps1' -1

class DigitalOceanImage
{
    [int] $Id
    [string] $Name
    [string] $Type
    [string] $Distribution
    [string] $Slug
    [bool] $Public
    [string[]] $Regions
    [datetime] $CreatedAt
    [string] $Status
    [string] $ErrorMessage
    [int] $SizeGigabytes
    [int] $MinDiskSize
    [string] $Description
    [hashtable] $Tags

    # Default constructor
    DigitalOceanImage()
    {
        $this.Id = 0
        $this.Name = ''
        $this.Type = ''
        $this.Distribution = ''
        $this.Slug = ''
        $this.Public = $false
        $this.Regions = @()
        $this.CreatedAt = [datetime]::MinValue
        $this.Status = ''
        $this.ErrorMessage = ''
        $this.SizeGigabytes = 0
        $this.MinDiskSize = 0
        $this.Description = ''
        $this.Tags = @{}
    }

    # Constructor with parameters
    DigitalOceanImage([PSCustomObject] $ImageObject)
    {
        $this.Id = if ($ImageObject.id)
        {
            [int]$ImageObject.id
        }
        else
        {
            0
        }
        $this.Name = if ($ImageObject.name)
        {
            [string]$ImageObject.name
        }
        else
        {
            ''
        }
        $this.Type = if ($ImageObject.type)
        {
            [string]$ImageObject.type
        }
        else
        {
            ''
        }
        $this.Distribution = if ($ImageObject.distribution)
        {
            [string]$ImageObject.distribution
        }
        else
        {
            ''
        }
        $this.Slug = if ($ImageObject.slug)
        {
            [string]$ImageObject.slug
        }
        else
        {
            ''
        }
        $this.Public = if ($null -ne $ImageObject.public)
        {
            [bool]$ImageObject.public
        }
        else
        {
            $false
        }
        # Handle regions - ensure it's always a string array
        if ($ImageObject.regions)
        {
            # Force array type by creating new array and adding items
            $this.Regions = @()
            if ($ImageObject.regions -is [array])
            {
                $this.Regions = $ImageObject.regions | ForEach-Object { [string]$_ }
            }
            else
            {
                $this.Regions = @([string]$ImageObject.regions)
            }
        }
        else
        {
            $this.Regions = @()
        }

        # Handle datetime conversion
        if ($ImageObject.created_at)
        {
            try
            {
                $this.CreatedAt = [datetime]::Parse($ImageObject.created_at)
            }
            catch
            {
                $this.CreatedAt = [datetime]::MinValue
            }
        }
        else
        {
            $this.CreatedAt = [datetime]::MinValue
        }

        $this.Status = if ($ImageObject.status)
        {
            [string]$ImageObject.status
        }
        else
        {
            ''
        }
        $this.ErrorMessage = if ($ImageObject.error_message)
        {
            [string]$ImageObject.error_message
        }
        else
        {
            ''
        }
        $this.SizeGigabytes = if ($ImageObject.size_gigabytes)
        {
            [int]$ImageObject.size_gigabytes
        }
        else
        {
            0
        }
        $this.MinDiskSize = if ($ImageObject.min_disk_size)
        {
            [int]$ImageObject.min_disk_size
        }
        else
        {
            0
        }
        $this.Description = if ($ImageObject.description)
        {
            [string]$ImageObject.description
        }
        else
        {
            ''
        }
        $this.Tags = if ($ImageObject.tags)
        {
            [hashtable]$ImageObject.tags
        }
        else
        {
            @{}
        }
    }

    # Method to convert to hashtable
    [hashtable] ToHashtable()
    {
        return @{
            Id            = $this.Id
            Name          = $this.Name
            Type          = $this.Type
            Distribution  = $this.Distribution
            Slug          = $this.Slug
            Public        = $this.Public
            Regions       = $this.Regions
            CreatedAt     = $this.CreatedAt
            Status        = $this.Status
            ErrorMessage  = $this.ErrorMessage
            SizeGigabytes = $this.SizeGigabytes
            MinDiskSize   = $this.MinDiskSize
            Description   = $this.Description
            Tags          = $this.Tags
        }
    }

    # Method to convert to string representation
    [string] ToString()
    {
        return "DigitalOceanImage: $($this.Name) (ID: $($this.Id), Type: $($this.Type))"
    }
}
#EndRegion '.\Classes\5.DigitalOceanImage.ps1' 201
#Region '.\Classes\DigitalOceanAccount.ps1' -1

class Team
{
    [string]$uuid
    [string]$name

    Team([string]$uuid, [string]$name)
    {
        $this.uuid = $uuid
        $this.name = $name
    }
}

class Account
{
    [int]$droplet_limit
    [int]$floating_ip_limit
    [string]$email
    [string]$name
    [string]$uuid
    [bool]$email_verified
    [string]$status
    [string]$status_message
    [Team]$team

    Account(
        [int]$droplet_limit,
        [int]$floating_ip_limit,
        [string]$email,
        [string]$name,
        [string]$uuid,
        [bool]$email_verified,
        [string]$status,
        [string]$status_message,
        [Team]$team
    )
    {
        $this.droplet_limit = $droplet_limit
        $this.floating_ip_limit = $floating_ip_limit
        $this.email = $email
        $this.name = $name
        $this.uuid = $uuid
        $this.email_verified = $email_verified
        $this.status = $status
        $this.status_message = $status_message
        $this.team = $team
    }
}

class Root
{
    [Account]$account

    Root([Account]$account)
    {
        $this.account = $account
    }
}
#EndRegion '.\Classes\DigitalOceanAccount.ps1' 58
#Region '.\Private\Get-DigitalOceanAPIAuthorizationBearerToken.ps1' -1

function Get-DigitalOceanAPIAuthorizationBearerToken
{
    <#
    .SYNOPSIS
    Get-DigitalOceanAPIAuthorizationBearerToken
 
    .DESCRIPTION
    Get Digital Ocean API Authorization Bearer Token.
 
    .EXAMPLE
    Get-DigitalOceanAPIAuthorizationBearerToken
 
    .OUTPUTS
    [System.String]
  #>



    [CmdletBinding()]
    [OutputType([System.String])]
    param()
    begin
    {
        #Content

    }
    process
    {
        [Environment]::GetEnvironmentVariable("DIGITALOCEAN_TOKEN", [System.EnvironmentVariableTarget]::User)
    }
    end
    {
        #Content

    }



}
#EndRegion '.\Private\Get-DigitalOceanAPIAuthorizationBearerToken.ps1' 39
#Region '.\Private\Invoke-DigitalOceanAPI.ps1' -1

function Invoke-DigitalOceanAPI
{
    <#
    .SYNOPSIS
    This private function invokes the DigitalOcean API.
    .DESCRIPTION
    This function is used to make API calls to DigitalOcean. It requires an API token to authenticate the request.
    .PARAMETER APIPath
    The API path to call, such as 'account', 'droplets', 'tags', etc.
    .PARAMETER APIVersion
    The API version to use, currently defaults to 'v2'.
    If you need to use a different version, you can specify it here.
    .PARAMETER Method
    The HTTP method to use.
    Valid values are 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', and 'PATCH'.
    Defaults to 'GET'.
    .PARAMETER Parameters
    The query parameters to include in the request.
    This should be a hashtable where the keys are parameter names and the values are parameter values.
    .EXAMPLE
    Invoke-DigitalOceanAPI -APIPath 'account' -APIVersion 'v2' -Method 'GET' -Parameters @{ page = 1; per_page = 10 }
    This example retrieves the account information from the DigitalOcean API.
    #>


    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $APIPath,

        [Parameter()]
        [String]
        $APIVersion = 'v2',

        [Parameter()]
        [ValidateSet('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH')]
        $Method = 'GET',

        [Parameter()]
        [hashtable]
        $Parameters
    )

    $Token = Get-DigitalOceanAPIAuthorizationBearerToken

    if ([string]::IsNullOrEmpty($Token))
    {
        throw "DigitalOcean API token is not set. Please set the DIGITALOCEAN_TOKEN environment variable."
    }

    $Headers = @{
        "Content-Type"  = "application/json"
        "Authorization" = "Bearer $($Token)"
    }

    if ($null -ne $Parameters -and $Parameters.Count -gt 0)
    {
        $query = ($Parameters.GetEnumerator() | ForEach-Object {
                "$([uri]::EscapeDataString($_.Key))=$([uri]::EscapeDataString($_.Value.ToString()))"
            }) -join "&"
    }
    if (-not [string]::IsNullOrEmpty($query))
    {
        $URI = "https://api.digitalocean.com/$($APIVersion)/$($APIPath)?$($query)"
    }
    else
    {
        $URI = "https://api.digitalocean.com/$($APIVersion)/$($APIPath)"
    }

    Write-Verbose "about to run $($URI)"
    $Response = Invoke-RestMethod -Method $Method -Headers $Headers -Uri $URI
    $Response
}
#EndRegion '.\Private\Invoke-DigitalOceanAPI.ps1' 76
#Region '.\Public\Get-DigitalOceanAccount.ps1' -1

function Get-DigitalOceanAccount
{
    <#
    .SYNOPSIS
    Get-DigitalOceanAccount
 
    .DESCRIPTION
    Retrieves Digital Ocean account information including account details, limits, and verification status. Supports pagination to retrieve multiple accounts or use the -All parameter to get all accounts at once.
 
    .PARAMETER Page
    Which 'page' of paginated results to return.
 
    .PARAMETER Limit
    Number of items returned per page.
 
    .PARAMETER All
    If you want to get all the images and not the Page / Limit.
 
    .EXAMPLE
    Get-DigitalOceanAccount -Page 1 -Limit 20
 
    .EXAMPLE
    Get-DigitalOceanAccount -All
 
    .LINK
    https://docs.digitalocean.com/reference/api/digitalocean/#tag/Account
 
    .OUTPUTS
    DigitalOcean.Account
  #>



    [CmdletBinding(DefaultParameterSetName = "Limit")]
    [OutputType('DigitalOcean.Account')]
    param
    (
        [int]
        [ValidateRange(1, 1000)]
        [Parameter(ParameterSetName = "Limit")]
        $Page = 1,

        [int]
        [ValidateRange(20, 200)]
        [Parameter(ParameterSetName = "Limit")]
        $Limit = 20,

        [Switch]
        [Parameter(ParameterSetName = "All")]
        $All
    )

    if ($All.IsPresent)
    {
        $Parameters = @{
            page     = 1
            per_page = 20
        }

        $AllArray = @()

        Write-Verbose "about to get all account from DigitalOcean"
        Write-Verbose "Page: 1, PerPage: 20"

        $response = Invoke-DigitalOceanAPI -APIPath account -Parameters $Parameters
        $Total = $response.meta.total
        Write-Verbose "DigitalOcean total account is $($Total)"

        $AllArray = $response.account

        do
        {
            # Check if there's a next page URL
            if ($response.links.pages.PSObject.Properties.Name -contains 'next' -and
                $null -ne $response.links.pages.next -and
                $response.links.pages.next -ne "")
            {
                $Split = $response.links.pages.next.Split('?')[1]
                if ($Split -match 'page=(\d+)&per_page=(\d+)')
                {
                    $Parameters = @{
                        page     = $matches[1]
                        per_page = $matches[2]
                    }
                    Write-Verbose "Page: $($matches[1]), PerPage: $($matches[2])"

                    Write-Verbose "the next url is $($response.links.pages.next)"
                    $response = Invoke-DigitalOceanAPI -APIPath account -Parameters $Parameters
                    $AllArray += $response.account
                }
                else
                {
                    # URL doesn't match pattern, break the loop
                    break
                }
            }
            else
            {
                # No next page, break the loop
                break
            }

        } while ($AllArray.Count -lt $Total)

        Write-Verbose "finished getting all sizes"

        # Convert API response objects to PowerShell class objects
        $ConvertedArray = @()
        foreach ($obj in $AllArray)
        {
            # Create Team object if team data exists
            $teamObject = $null
            if ($obj.team)
            {
                $teamObject = [Team]::new(
                    $(if ($null -ne $obj.team.uuid) { $obj.team.uuid } else { "" }),
                    $(if ($null -ne $obj.team.name) { $obj.team.name } else { "" })
                )
            }

            # Create Account object with all properties, providing defaults for missing values
            $accountObject = [Account]::new(
                $(if ($null -ne $obj.droplet_limit) { $obj.droplet_limit } else { 0 }),
                $(if ($null -ne $obj.floating_ip_limit) { $obj.floating_ip_limit } else { 0 }),
                $(if ($null -ne $obj.email) { $obj.email } else { "" }),
                $(if ($null -ne $obj.name) { $obj.name } else { "" }),
                $(if ($null -ne $obj.uuid) { $obj.uuid } else { "" }),
                $(if ($null -ne $obj.email_verified) { $obj.email_verified } else { $false }),
                $(if ($null -ne $obj.status) { $obj.status } else { "" }),
                $(if ($null -ne $obj.status_message) { $obj.status_message } else { "" }),
                $teamObject
            )

            $ConvertedArray += $accountObject
        }
        $ConvertedArray

    }
    else
    {
        $Parameters = @{
            page     = $Page
            per_page = $Limit
        }

        $AllArray = @()
        $response = Invoke-DigitalOceanAPI -APIPath account -Parameters $Parameters
        $AllArray = $response.account

        # Convert API response objects to PowerShell class objects
        $ConvertedArray = @()
        foreach ($obj in $AllArray)
        {
            # Create Team object if team data exists
            $teamObject = $null
            if ($obj.team)
            {
                $teamObject = [Team]::new(
                    $(if ($null -ne $obj.team.uuid) { $obj.team.uuid } else { "" }),
                    $(if ($null -ne $obj.team.name) { $obj.team.name } else { "" })
                )
            }

            # Create Account object with all properties, providing defaults for missing values
            $accountObject = [Account]::new(
                $(if ($null -ne $obj.droplet_limit) { $obj.droplet_limit } else { 0 }),
                $(if ($null -ne $obj.floating_ip_limit) { $obj.floating_ip_limit } else { 0 }),
                $(if ($null -ne $obj.email) { $obj.email } else { "" }),
                $(if ($null -ne $obj.name) { $obj.name } else { "" }),
                $(if ($null -ne $obj.uuid) { $obj.uuid } else { "" }),
                $(if ($null -ne $obj.email_verified) { $obj.email_verified } else { $false }),
                $(if ($null -ne $obj.status) { $obj.status } else { "" }),
                $(if ($null -ne $obj.status_message) { $obj.status_message } else { "" }),
                $teamObject
            )

            $ConvertedArray += $accountObject
        }
        $ConvertedArray
    }
}
#EndRegion '.\Public\Get-DigitalOceanAccount.ps1' 181
#Region '.\Public\Get-DigitalOceanImage.ps1' -1

function Get-DigitalOceanImage
{
    <#
      .SYNOPSIS
      Get-DigitalOceanImage.
 
      .DESCRIPTION
      Retrieves Digital Ocean Image(s) from the DigitalOcean API with support for pagination and filtering.
 
      .PARAMETER Type
      The type of the image, it can be 'application', 'distribution'
      If no value supply you get all images.
 
      .PARAMETER Page
      Which 'page' of paginated results to return.
 
      .PARAMETER Limit
      Number of items returned per page.
 
      .PARAMETER All
      If you want to get all the images and not the Page / Limit.
 
      .EXAMPLE
      Get-DigitalOceanImage -Type application -Page 1 -Limit 21
 
      .EXAMPLE
      Get-DigitalOceanImage -Type distribution -Page 1 -Limit 21
 
      .EXAMPLE
      Get-DigitalOceanImage -All
 
      .LINK
      https://docs.digitalocean.com/reference/api/digitalocean/#tag/Images/operation/images_list
 
      .OUTPUTS
      DigitalOceanImage
  #>


    [CmdletBinding(DefaultParameterSetName = "Limit")]
    [OutputType([DigitalOceanImage[]])]
    param
    (
        [Parameter(ParameterSetName = "Limit")]
        [ValidateSet('application', 'distribution', '')]
        [String]
        $Type,

        [Parameter(ParameterSetName = "Limit")]
        [ValidateRange(1, 1000)]
        [int]
        $Page = 1,

        [Parameter(ParameterSetName = "Limit")]
        [ValidateRange(20, 200)]
        [int]
        $Limit = 20,

        [Parameter(ParameterSetName = "All")]
        [Switch]
        $All
    )

    if ($All.IsPresent)
    {
        $Parameters = @{
            page     = 1
            per_page = 20
        }

        $AllImages = @()

        Write-Verbose "about to get all images from DigitalOcean"
        Write-Verbose "Page: 1, PerPage: 20"

        $response = Invoke-DigitalOceanAPI -APIPath images -Parameters $Parameters

        if ($null -eq $response -or $null -eq $response.images)
        {
            throw "Invalid or null response from API"
        }

        $Total = $response.meta.total
        Write-Verbose "DigitalOcean total images is $($Total)"

        $AllImages = $response.images

        do
        {
            if ($response.links.pages.next)
            {
                $Split = $response.links.pages.next.Split('?')[1]
                if ($Split -match 'page=(\d+)&per_page=(\d+)')
                {
                    $Parameters = @{
                        page     = $matches[1]
                        per_page = $matches[2]
                    }
                    Write-Verbose "Page: $($matches[1]), PerPage: $($matches[2])"

                    $response = Invoke-DigitalOceanAPI -APIPath images -Parameters $Parameters
                    $AllImages += $response.images
                }
                else
                {
                    break
                }
            }
            else
            {
                break
            }
        } while ($response.links.pages.next -and $AllImages.Count -lt $Total)

        Write-Verbose "finished getting all images"

        # Convert to DigitalOceanImage objects
        if ($AllImages.Count -gt 0)
        {
            $digitalOceanImages = @()
            foreach ($obj in $AllImages)
            {
                $digitalOceanImages += [DigitalOceanImage]::new($obj)
            }
            Write-Output $digitalOceanImages
        }
        else
        {
            # Return empty array for empty response
            Write-Output @([DigitalOceanImage[]]@())
        }

    }
    else
    {
        $Parameters = @{
            page     = $Page
            per_page = $Limit
        }

        if (-not [string]::IsNullOrEmpty($Type))
        {
            $Parameters.Add('type', $Type)
        }

        $response = Invoke-DigitalOceanAPI -APIPath images -Parameters $Parameters

        if ($null -eq $response -or $null -eq $response.images)
        {
            throw "Invalid or null response from API"
        }

        $AllImages = $response.images

        # Convert to DigitalOceanImage objects
        if ($AllImages.Count -gt 0)
        {
            $digitalOceanImages = @()
            foreach ($obj in $AllImages)
            {
                $digitalOceanImages += [DigitalOceanImage]::new($obj)
            }
            Write-Output $digitalOceanImages
        }
        else
        {
            # Return empty array for empty response
            Write-Output @([DigitalOceanImage[]]@())
        }
    }
}
#EndRegion '.\Public\Get-DigitalOceanImage.ps1' 171