Plugins/LeaseWeb.ps1

function Get-CurrentPluginType { 'dns-01' }

function Add-DnsTxt {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        [string]$RecordName,
        [Parameter(Mandatory,Position=1)]
        [string]$TxtValue,
        [Parameter(Mandatory)]
        [securestring]$LSWApiKey,
        [string]$LSWApiBase = 'https://api.leaseweb.com',
        [Parameter(ValueFromRemainingArguments)]
        $ExtraParams
    )

    # Add the API key to an auth header
    $apikey = [pscredential]::new('a',$LSWApiKey).GetNetworkCredential().Password
    $authHeader = @{ 'x-lsw-auth' = $apikey }

    # Find the domain for the record
    if (-not ($zone = Find-LSWZone $RecordName $LSWApiBase $authHeader)) {
        throw "Domain match not found for $RecordName."
    }
    Write-Verbose "Found domain $zone"

    # check for existing record
    $rec = Get-LSWTxtRecord $RecordName $zone $LSWApiBase $authHeader

    if ($rec) {
        if ("`"$TxtValue`"" -in $rec.content) {
            Write-Debug "Record $RecordName already contains $TxtValue. Nothing to do."
        } else {
            # update the existing record
            $queryParams = @{
                Uri = "$LSWApiBase/hosting/v2/domains/$zone/resourceRecordSets/$RecordName/TXT"
                Method = 'PUT'
                Body = [ordered]@{
                    content = $rec.content + @( "`"$TxtValue`"" )
                    ttl = 60
                } | ConvertTo-Json
                Headers = $authHeader
                ContentType = 'application/json'
                ErrorAction = 'Stop'
                Verbose = $false
            }
            Write-Debug "PUT $($queryParams.Uri)`n$($queryParams.Body)"
            Write-Verbose "Adding TXT record value $TxtValue to $RecordName"
            try {
                Invoke-RestMethod @queryParams @script:UseBasic | Out-Null
            } catch { throw }
        }
    } else {
        # create a new record from scratch
        $queryParams = @{
            Uri = "$LSWApiBase/hosting/v2/domains/$zone/resourceRecordSets"
            Method = 'POST'
            Body = [ordered]@{
                name = $RecordName
                type = 'TXT'
                content = @( "`"$TxtValue`"" )
                ttl = 60
            } | ConvertTo-Json
            Headers = $authHeader
            ContentType = 'application/json'
            ErrorAction = 'Stop'
            Verbose = $false
        }
        Write-Debug "POST $($queryParams.Uri)`n$($queryParams.Body)"
        Write-Verbose "Adding new TXT record for $RecordName"
        try {
            Invoke-RestMethod @queryParams @script:UseBasic | Out-Null
        } catch { throw }
    }


    <#
    .SYNOPSIS
        Add a DNS TXT record to LeaseWeb
 
    .DESCRIPTION
        Add a DNS TXT record to LeaseWeb
 
    .PARAMETER RecordName
        The fully qualified name of the TXT record.
 
    .PARAMETER TxtValue
        The value of the TXT record.
 
    .PARAMETER LSWApiKey
        The API key for your LeaseWeb account.
 
    .PARAMETER LSWApiBase
        The root url for the LeaseWeb API. Defaults to 'https://api.leaseweb.com'
 
    .PARAMETER ExtraParams
        This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports.
 
    .EXAMPLE
        $apikey = Read-Host "API Key" -AsSecureString
        Add-DnsTxt '_acme-challenge.example.com' 'txt-value' -LSWApiKey $apikey
 
        Adds a TXT record for the specified site with the specified value.
    #>

}

function Remove-DnsTxt {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        [string]$RecordName,
        [Parameter(Mandatory,Position=1)]
        [string]$TxtValue,
        [Parameter(Mandatory)]
        [securestring]$LSWApiKey,
        [string]$LSWApiBase = 'https://api.leaseweb.com',
        [Parameter(ValueFromRemainingArguments)]
        $ExtraParams
    )

    # Add the API key to an auth header
    $apikey = [pscredential]::new('a',$LSWApiKey).GetNetworkCredential().Password
    $authHeader = @{ 'x-lsw-auth' = $apikey }

    # Find the domain for the record
    if (-not ($zone = Find-LSWZone $RecordName $LSWApiBase $authHeader)) {
        throw "Domain match not found for $RecordName."
    }
    Write-Verbose "Found domain $zone"

    # check for existing record
    $rec = Get-LSWTxtRecord $RecordName $zone $LSWApiBase $authHeader

    if ($rec -and "`"$TxtValue`"" -in $rec.content) {
        if ($rec.content.Count -eq 1) {
            # delete the record entirely
            $queryParams = @{
                Uri = "$LSWApiBase/hosting/v2/domains/$zone/resourceRecordSets/$RecordName/TXT"
                Method = 'DELETE'
                Headers = $authHeader
                ContentType = 'application/json'
                ErrorAction = 'Stop'
                Verbose = $false
            }
            Write-Debug "DELETE $($queryParams.Uri)"
            Write-Verbose "Removing TXT record for $RecordName"
            try {
                Invoke-RestMethod @queryParams @script:UseBasic | Out-Null
            } catch { throw }
        } else {
            # update the record to remove this value
            $queryParams = @{
                Uri = "$LSWApiBase/hosting/v2/domains/$zone/resourceRecordSets/$RecordName/TXT"
                Method = 'PUT'
                Body = [ordered]@{
                    content = @( $rec.content | Where-Object { $_ -ne "`"$TxtValue`"" } )
                    ttl = 60
                } | ConvertTo-Json
                Headers = $authHeader
                ContentType = 'application/json'
                ErrorAction = 'Stop'
                Verbose = $false
            }
            Write-Debug "PUT $($queryParams.Uri)`n$($queryParams.Body)"
            Write-Verbose "Removing TXT record value $TxtValue from $RecordName"
            try {
                Invoke-RestMethod @queryParams @script:UseBasic | Out-Null
            } catch { throw }
        }
    } else {
        Write-Debug "Could not find record $RecordName with $TxtValue to delete. Nothing to do."
    }

    <#
    .SYNOPSIS
        Remove a DNS TXT record from LeaseWeb
 
    .DESCRIPTION
        Remove a DNS TXT record from LeaseWeb
 
    .PARAMETER RecordName
        The fully qualified name of the TXT record.
 
    .PARAMETER TxtValue
        The value of the TXT record.
 
    .PARAMETER LSWApiKey
        The API key for your LeaseWeb account.
 
    .PARAMETER LSWApiBase
        The root url for the LeaseWeb API. Defaults to 'https://api.leaseweb.com'
 
    .PARAMETER ExtraParams
        This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports.
 
    .EXAMPLE
        $apikey = Read-Host "API Key" -AsSecureString
        Remove-DnsTxt '_acme-challenge.example.com' 'txt-value' -LSWApiKey $apikey
 
        Removes a TXT record for the specified site with the specified value.
    #>

}

function Save-DnsTxt {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromRemainingArguments)]
        $ExtraParams
    )
    <#
    .SYNOPSIS
        Not required.
 
    .DESCRIPTION
        This provider does not require calling this function to commit changes to DNS records.
 
    .PARAMETER ExtraParams
        This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports.
    #>

}

############################
# Helper Functions
############################

# https://developer.leaseweb.com/

function Find-LSWZone {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        [string]$RecordName,
        [Parameter(Mandatory,Position=1)]
        [string]$ApiBase,
        [Parameter(Mandatory,Position=2)]
        [hashtable]$AuthHeader
    )

    # setup a module variable to cache the record to zone mapping
    # so it's quicker to find later
    if (!$script:LSWRecordZones) { $script:LSWRecordZones = @{} }

    # check for the record in the cache
    if ($script:LSWRecordZones.ContainsKey($RecordName)) {
        return $script:LSWRecordZones.$RecordName
    }

    # We can't make any assumptions about the portions of the FQDN that make
    # up the root domain. So ask the API for about each possibility, longest to
    # shortest until we find a match.
    $pieces = $RecordName.Split('.')
    for ($i=0; $i -lt ($pieces.Count-1); $i++) {
        $zoneTest = $pieces[$i..($pieces.Count-1)] -join '.'
        Write-Debug "Checking $zoneTest"
        try {
            $queryParams = @{
                Uri = "$ApiBase/hosting/v2/domains/$zoneTest"
                Headers = $AuthHeader
                ContentType = 'application/json'
                ErrorAction = 'Stop'
                Verbose = $false
            }
            Write-Debug "GET $($queryParams.Uri)"
            $response = Invoke-RestMethod @queryParams @script:UseBasic
        } catch {
            # 400/404 responses mean the zone wasn't found, so skip to the next check
            if ($_.Exception.Response.StatusCode -in 400,404) {
                continue
            }
            # re-throw anything else
            throw
        }

        if ($response) {
            $script:LSWRecordZones.$RecordName = $response.domainName
            return $response.domainName
        }
    }

    return $null

}

function Get-LSWTxtRecord {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        [string]$RecordName,
        [Parameter(Mandatory,Position=1)]
        [string]$ZoneName,
        [Parameter(Mandatory,Position=2)]
        [string]$ApiBase,
        [Parameter(Mandatory,Position=3)]
        [hashtable]$AuthHeader
    )

    try {
        $queryParams = @{
            Uri = "$ApiBase/hosting/v2/domains/$ZoneName/resourceRecordSets/$RecordName/TXT"
            Headers = $AuthHeader
            ContentType = 'application/json'
            ErrorAction = 'Stop'
            Verbose = $false
        }
        Write-Debug "GET $($queryParams.Uri)"
        $response = Invoke-RestMethod @queryParams @script:UseBasic
    } catch {
        # 404 responses mean the record wasn't found, so just return
        if (404 -eq $_.Exception.Response.StatusCode) {
            return
        }
        # re-throw anything else
        throw
    }

    return $response
}