NewRelicPS.SyntheticMonitors.psm1


<#
.Synopsis
  Gets New Relic Synthetic Monitor Locations
.Description
  Returns a list of valid synthetic monitor locations
.Example
  Get-NRSyntheticLocationList -APIKey $APIKey
  Returns a list of synthetic monitor locations

.Parameter APIKey
  This must be an Admin API key. See more here: https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys
#>


Function Get-NRSyntheticLocationList {
  [CMDLetBinding()]
  Param (
    [Parameter (Mandatory = $true)]
    [string] $APIKey
  )
  $headers = @{
    'X-Api-Key' = $APIKey
  }
  $url = 'https://synthetics.newrelic.com/synthetics/api/v1/locations'

  # Call the API
  Invoke-RestMethod -Uri $url -Headers $headers -Method 'Get' -ContentType 'application/json'
}

<#
.Synopsis
  Gets New Relic Synthetic Monitors
.Description
  Gets all New Relic synthetic monitors in an account or returns a specific monitor if a monitor ID is provided. This command only works with synthetic condition types.
.Example
  Get-NRSyntheticMonitor -APIKey $APIKey -AccountId 12345678
  Gets information on all New Relic synthetic monitors in the account associated with the API key provided.
.Example
  Get-NRSyntheticMonitor -APIKey $APIKey -Id 123
  Returns only data for the monitor with id 123 in the account associated with the API key provided. Returns null if no existing monitor has the ID provided.
.Parameter APIKey
  This must be an Admin API key. See more here: https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys
.Parameter Id
  Filters results to the single monitor with the Id specified. Returns null if a monitor with the specified Id is not found.
#>


Function Get-NRSyntheticMonitor {
  [CMDLetBinding()]
  Param (
    [Parameter (Mandatory = $true)]
    [string] $APIKey,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [string] $Id
  )
  Begin {
    $headers = @{
      'X-Api-Key' = $APIKey
    }
  }
  Process {
    $url = 'https://synthetics.newrelic.com/synthetics/api/v3/monitors'
    If ($Id) {
      $url += "/$Id"
    }

    # Call the API and return null if a monitor ID is provided but not found
    try {
      $result = Invoke-RestMethod -Uri $url -Headers $headers -Method 'Get' -ContentType 'application/json'
    }
    catch {
      If ($_.Exception.Response.StatusCode.Value__ -ne '404') {
        Write-Error $_
      }
    }

    # The result is wrapped in the monitors property unless a single result is returned so use turnary to return consistent results
    $result.monitors ? $result.monitors : $result
  }
}

<#
.Synopsis
  Gets New Relic Synthetic Monitor Script
.Description
  Gets the Base64 encoded script contents for a Synthetic Monitor.
.Example
  Get-NRSyntheticMonitorScript -APIKey $APIKey -Id 12345678
  Gets the script content for monitor Id 12345678
.Parameter APIKey
  This must be an Admin API key. See more here: https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys
.Parameter Id
  The Id for the Synthetic monitor to return the script content from. Must be of type SCRIPT_API or SCRIPT_BROWSER.
#>


Function Get-NRSyntheticMonitorScript {
  [CMDLetBinding()]
  Param (
    [Parameter (Mandatory = $true)]
    [string] $APIKey,
    [Parameter (Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
    [string] $Id
  )
  Begin {
    $headers = @{
      'X-Api-Key' = $APIKey
    }
  }
  Process {
    $url = "https://synthetics.newrelic.com/synthetics/api/v3/monitors/$Id/script"

    # Call the API and return null if a monitor ID is provided but not found
    Invoke-RestMethod -Uri $url -Headers $headers -Method 'Get' -ContentType 'application/json'
  }
}

<#
.Synopsis
  Creates a New Relic Synthetic Monitor
.Description
  Creates a New Relic synthetic monitor.
.Example
  New-NRSyntheticMonitor -APIKey $APIKey -Name 'MySyntheticMonitor' -Locations 'AWS_US_EAST_1,AWS_US_EAST_2' -URI 'https://some.net'
  Creates a new Synthetics monitor which pings 'https://some.net' every 10 minutes (by default) from two locations.
.Example
  ScriptVariables = @{ENVIRONMENT_DOMAIN='some.net'}
  New-NRSyntheticMonitor -APIKey $APIKey -Type 'Script_API' -ScriptVariables $ScriptVariables -ScriptPath $scriptPath -Name 'MyScriptedSyntheticMonitor' -Locations 'AWS_US_EAST_1,AWS_US_EAST_2' -URI 'https://some.net'
  Creates a new scripted Synthetics monitor and templates in the provided domain name to the script at $scriptPath during runtime
.Parameter APIKey
  This must be an Admin API key. See more here: https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys
.Parameter BypassHeadRequest
  Skips the default HEAD request and instead uses the GET verb with a ping check. Defaults to True. Valid only for SIMPLE monitors.
.Parameter Frequency
  Determines how often the monitor pings the resource in minutes. Defaults to 10. Valid options are 1, 5, 10, 15, 30, 60, 360, 720, or 1440.
.Parameter Locations
  An array of locations to probe from. Get available options by running the Get-NRSyntheticLocation CMDLet.
.Parameter Name
  The friendly name used to identify the monitor.
.Parameter ScriptVariables
  A hash table of scriptvariables to replace in a monitor's script.
.Parameter ScriptPath
  File path for the script to be used for scripted monitors. Valid only for SCRIPT_API and SCRIPT_BROWSER.
.Parameter SlaThreshold
  The number of seconds for a check to finish witin to be considered satisfying. Defaults to 7. Checks finishing between 1-4 times this value are considered tolerated and above 4x is considered frustrating.
.Parameter Status
  The status of the newly created monitor. Defaults to ENABLED. Valid options are ENABLED, MUTED, DISABLED.
.Parameter TreatRedirectAsFailure
  Whether any redirect will result in a failure. Defaults to False. Valid only for SIMPLE monitors.
.Parameter Type
  Specifies which type of synthetic monitor to create. Defaults to 'SIMPLE'. Valid options are 'SIMPLE','BROWSER','SCRIPT_API','SCRIPT_BROWSER'.
.Parameter URI
  The URI of the resource to ping. Required for SIMPLE and BROWSER monitor types.
.Parameter ValidationString
  A string expected to be returned in valid responses. Valid only for SIMPLE and BROWSER monitors.
.Parameter VerifySSL
  Specifies whether each check will run a detailed OpenSSL handshake and alert on failures. Defaults to false. Valid only for SIMPLE and BROWSER monitors.
#>


Function New-NRSyntheticMonitor {
  [CMDLetBinding(SupportsShouldProcess=$true)]
  Param (
    [Parameter (Mandatory = $true)]
    [string] $APIKey,
    [Parameter (Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
    [string] $Name,
    [Parameter (Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
    [array] $Locations,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [boolean] $BypassHeadRequest = $true,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [ValidateSet (1,5, 10, 15, 30, 60, 360, 720, 1440)]
    [Int32] $Frequency= 10,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [object] $ScriptVariables = @{},
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [string] $ScriptPath,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [Double] $SlaThreshold = 7,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [ValidateSet ('ENABLED', 'MUTED', 'DISABLED')]
    [string] $Status = 'ENABLED',
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [boolean] $TreatRedirectAsFailure,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [ValidateSet ('SIMPLE','BROWSER','SCRIPT_API','SCRIPT_BROWSER')]
    [string] $Type = 'SIMPLE',
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [string] $URI,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [string] $ValidationString,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [boolean] $VerifySSL = $false
  )
  Begin {
    $url = 'https://synthetics.newrelic.com/synthetics/api/v3/monitors'
    $headers = @{
      'X-Api-Key' = $APIKey
    }
  }
  Process {
    # Check for URI on Simple and Browser type creation requests
    If (($Type -eq 'SIMPLE' -or $Type -eq 'BROWSER') -and [string]::IsNullOrWhiteSpace($URI)){
      Write-Error "A URI is required when creating a monitor of type $Type."
    }

    # Build up the body of the request
    $Body = @{
      name = $Name
      type = $Type
      frequency = $Frequency
      locations = $Locations
      status = $Status
      slaThreshold = $SlaThreshold
    }

    # Simple and browser monitors have some unique parameters
    If ($Type -eq 'SIMPLE' -or $Type -eq 'BROWSER') {
      $monitorOptions = @{
        verifySSL = $VerifySSL
      }

      If ($ValidationString) {
        $monitorOptions += @{
          validationString = $ValidationString
        }
      }

      # Some parameters are only valid with SIMPLE monitors
      If ($Type -eq 'SIMPLE') {
        $monitorOptions += @{
          bypassHEADRequest = $BypassHeadRequest
          treatRedirectAsFailure = $TreatRedirectAsFailure
        }
      }

      # Add parameters specific to simple and browser monitors
      $Body += @{
        uri = $URI
        options = $monitorOptions
      }
    }

    # Call the API
    If ($PSCmdlet.ShouldProcess($Name, "Create synthetic monitor")) {
      Invoke-RestMethod -Uri $url -Headers $headers -Body ($Body | ConvertTo-Json) -Method 'Post' -ContentType 'application/json'
    }

    # The API requires the monitor to be created in one call and then updated with a script in a second call.
    If ($Type -eq 'SCRIPT_API' -or $Type -eq 'SCRIPT_BROWSER') {
      If ([string]::IsNullOrWhiteSpace($ScriptPath) -or -NOT (Test-Path $ScriptPath)) {
        Write-Error "A valid script path is required when creating a monitor of type $Type."
      }

      # Get templated and encoded script content
      $scriptContent = Get-NRScriptContent -ScriptPath $ScriptPath -ScriptVariables $ScriptVariables

      # Get the new monitor's Id because the API does not return it from the post call
      $monitor = Get-NRSyntheticMonitor -APIKey $APIKey | Where-Object {$_.name -eq $Name}

      # Throw an error if the monitor isn't found or if there are more than 1 monitor returned
      Switch ($monitor.name.count) {
        0 {Write-Error "There was a problem setting the script on the monitor. The monitor with name $Name was not found" }
        {$_ -gt 1} {Write-Error "There was a problem setting the script on the monitor. More than one monitor was found with the name $Name"}
      }

      Update-NRSyntheticMonitorScript -APIKey $APIKey -Id $monitor.id -ScriptContent $scriptContent
    }
  }
}

<#
.Synopsis
  Deletes a New Relic Synthetic Monitor
.Description
  Deletes a New Relic synthetic monitor.
.Example
  Remove-NRSyntheticMonitor -APIKey $APIKey -Id '123456'
  Deletes the synthetic monitor with ID 123456 from the account associated with the provided API key
.Example
  $Ids | Remove-NRSyntheticMonitor -APIKey $APIKey
  Deletes all syntheic monitors whose Id are listed in $Ids from the account associated with the provided API key
.Parameter APIKey
  This must be an Admin API key. See more here: https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys
.Parameter Id
  The unique Id of the monitor to remove.
#>


Function Remove-NRSyntheticMonitor {
  [CMDLetBinding(SupportsShouldProcess=$true)]
  Param (
    [Parameter (Mandatory = $true)]
    [string] $APIKey,
    [Parameter (Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [string] $Id
  )
  Begin {
    $headers = @{
      'X-Api-Key' = $APIKey
    }
  }
  Process {
    $url = "https://synthetics.newrelic.com/synthetics/api/v3/monitors/$Id"

    # Call the API
    If ($PSCmdlet.ShouldProcess($Id, "Remove synthetic monitor")) {
      Invoke-RestMethod -Uri $url -Headers $headers -Method 'Delete' -ContentType 'application/json'
    }
  }
}


<#
.Synopsis
  Updates a New Relic Synthetic Monitor
.Description
  Updates a New Relic synthetic monitor.
.Example
  Update-NRSyntheticMonitor -APIKey $APIKey -Id '123456' -Frequency 5
  Updates the monitor with ID 123456 so that it is checked every 5 minutes.
.Example
  $Ids | Update-NRSyntheticMonitor -APIKey $APIKey -Id '123456' -SlaThreshold 4.25
  Updates all monitors in the $Ids array with an SlaThreshold of 4.25 and leaves all other existing properties untouched.
.Example
  ScriptVariables = @{ENVIRONMENT_DOMAIN='some.net'}
  Update-NRSyntheticMonitor -APIKey $APIKey -Id '123456' -Type 'Script_API' -ScriptVariables $ScriptVariables -ScriptPath $scriptPath
  Updates a scripted Synthetics monitor and templates in the provided domain name to the script at $scriptPath during runtime
.Parameter APIKey
  This must be an Admin API key. See more here: https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys
.Parameter BypassHeadRequest
  Skips the default HEAD request and instaed uses the GET verb with a ping check. Defaults to True. Valid only for SIMPLE monitors.
.Parameter Frequency
  Determines how often the monitor pings the resource in minutes. Defaults to 10. Valid options are 1, 5, 10, 15, 30, 60, 360, 720, or 1440.
.Parameter Id
  The Id of the monitor to update.
.Parameter Locations
  An array of locations to probe from. Get available options by running the Get-NRSyntheticLocation CMDLet.
.Parameter Name
  The friendly name used to identify the monitor.
.Parameter ScriptVariables
  A hash table of scriptvariables to replace in a monitor's script.
.Parameter ScriptPath
  File path for the script to be used for scripted monitors. Valid only for SCRIPT_API and SCRIPT_BROWSER.
.Parameter SlaThreshold
  The number of seconds for a check to finish witin to be considered satisfying. Defaults to 7. Checks finishing between 1-4 times this value are considered tolerated and above 4x is considered frustrating.
.Parameter Status
  The status of the newly created monitor. Defaults to ENABLED. Valid options are ENABLED, MUTED, DISABLED.
.Parameter TreatRedirectAsFailure
  Whether any redirect will result in a failure. Defaults to False. Valid only for SIMPLE monitors.
.Parameter URI
  The URI of the resource to ping. Required for SIMPLE and BROWSER monitor types.
.Parameter ValidationString
  A string expected to be returned in valid responses. Valid only for SIMPLE and BROWSER monitors.
.Parameter VerifySSL
  Specifies whether each check will run a detailed OpenSSL handshake and alert on failures. Defaults to false. Valid only for SIMPLE and BROWSER monitors.
#>

Function Update-NRSyntheticMonitor {
  [CMDLetBinding(SupportsShouldProcess=$true)]
  Param (
    [Parameter (Mandatory = $true)]
    [string] $APIKey,
    [Parameter (Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [string] $Id,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [Nullable[boolean]] $BypassHeadRequest,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [ValidateSet (1,5, 10, 15, 30, 60, 360, 720, 1440)]
    [Int32] $Frequency,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [array] $Locations,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [string] $Name,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [object] $ScriptVariables = @{},
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [string] $ScriptPath,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [Double] $SlaThreshold,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [ValidateSet ('ENABLED', 'MUTED', 'DISABLED')]
    [string] $Status,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [Nullable[boolean]] $TreatRedirectAsFailure,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [string] $URI,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [string] $ValidationString,
    [Parameter (ValueFromPipelineByPropertyName = $true)]
    [Nullable[boolean]] $VerifySSL
  )
  Begin {
    $headers = @{
      'X-Api-Key' = $APIKey
    }
  }
  Process {
    $url = "https://synthetics.newrelic.com/synthetics/api/v3/monitors/$Id"

    # Build up the list of top-level fields to update
    $FieldsToUpdate = @{}
    Switch ($true) {
      { -NOT [string]::IsNullOrWhiteSpace($Name) } { $FieldsToUpdate += @{ name =$Name }}
      { $Frequency } { $FieldsToUpdate += @{ frequency = $Frequency }}
      { -NOT [string]::IsNullOrWhiteSpace($URI) } { $FieldsToUpdate += @{ uri = $URI }}
      { $Locations } { $FieldsToUpdate += @{ locations = $Locations }}
      { $Status } { $FieldsToUpdate += @{ status = $Status }}
      { $SlaThreshold } { $FieldsToUpdate += @{ slaThreshold = $SlaThreshold }}
    }

    # The options object is not idempotent by default so updating a value could remove other values by default
    If ("$ValidationString" -or "$VerifySSL" -or "$BypassHeadRequest" -or "$TreatRedirectAsFailure") {

      # Get the existing options
      $ExistingOptions = (Get-NRSyntheticMonitor -APIKey $APIKey -Id $Id).options

      # Use turnary operator to merge existing options with user provided options. If neither an existing or user provided option exists it is sent as null.
      $FieldsToUpdate['options'] += @{
        validationString = $ValidationString ? $ValidationString : $ExistingOptions.validationString
        verifySSL = "$VerifySSL" ? $($VerifySSL.tostring().tolower()) : $ExistingOptions.verifySSL
        bypassHEADRequest = "$BypassHeadRequest" ? $($BypassHeadRequest.tostring().tolower()) : $ExistingOptions.bypassHEADRequest
        treatRedirectAsFailure = "$treatRedirectAsFailure" ? $($TreatRedirectAsFailure.tostring().tolower()) : $ExistingOptions.treatRedirectAsFailure
      }
    }

    # Only update the monitor if there is something to update
    If ($FieldsToUpdate.keys.count -gt 0) {

      # Call the API
      If ($PSCmdlet.ShouldProcess($Id, "Update synthetic monitor")) {
        Invoke-RestMethod -Uri $url -Headers $headers -Body ($FieldsToUpdate | ConvertTo-Json) -Method 'Patch' -ContentType 'application/json'
      }
    }

    # Update the monitor script if provided
    If (-NOT [string]::IsNullOrWhiteSpace($ScriptPath)) {

      # Get templated and encoded script content
      $scriptContent = Get-NRScriptContent -ScriptPath $ScriptPath -ScriptVariables $ScriptVariables

      # Call the update CMDLet
      If ($PSCmdlet.ShouldProcess($Id, "Update synthetic monitor script")) {
        Update-NRSyntheticMonitorScript -APIKey $APIKey -Id $Id -ScriptContent $scriptContent
      }
    }
  }
}

<#
.Synopsis
  Updates a New Relic Synthetic Monitor Script
.Description
  Updates a New Relic synthetic monitor script
.Example
  Update-NRSyntheticMonitorScript -APIKey $APIKey -Id '123456' -ScriptContent $Base64EncodedScript
  Updates the monitor with ID 123456 with the script content provided
.Parameter APIKey
  This must be an Admin API key. See more here: https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys
.Parameter Id
  The unique Id for the Synthetic monitor to udpate with script content. Must be a SCRIPT_API or SCRIPT_BROWSER type.
.Parameter ScriptContent
  The Base64 encoded script to be run for the monitor.
#>

Function Update-NRSyntheticMonitorScript {
  [CMDLetBinding(SupportsShouldProcess=$true)]
  Param (
    [Parameter (Mandatory = $true)]
    [string] $APIKey,
    [Parameter (Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [string] $Id,
    [Parameter (Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
    [string] $ScriptContent
  )
  Begin {
    $url = "https://synthetics.newrelic.com/synthetics/api/v3/monitors/$Id/script"
    $headers = @{
      'X-Api-Key' = $APIKey
    }
  }
  Process {

    # Build up the body of the request
    $body = @{
      scriptText = $ScriptContent
    } | ConvertTo-Json

    # Call the API
    If ($PSCmdlet.ShouldProcess($Name, "Create synthetic monitor")) {
      Invoke-RestMethod -Uri $url -Headers $headers -Body $body -Method 'Put' -ContentType 'application/json'
    }
  }
}

##############################
# Internal Functions
##############################

Function Get-NRScriptContent {
  Param(
    [Parameter (Mandatory = $true)]
    [string] $ScriptPath,
    [object] $ScriptVariables
  )
  If (Test-Path $ScriptPath) {
    $scriptContent = Get-Content $ScriptPath -Raw
    Foreach ($key in $ScriptVariables.keys) {
      $scriptContent = $scriptContent.replace("`${$key}", $ScriptVariables.$key)
    }
    Return [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($scriptContent))
  }
  Else {
    Throw [System.IO.FileNotFoundException]::new("Unable to get Synthetics script content. File not found at path $scriptPath.")
  }
}