NewRelicPS.Configuration.SyntheticMonitor.psm1

Using module '.\NewRelicPS.SyntheticMonitors.psm1'

<#
.Synopsis
  Idempotently applies New Relic synthetic monitor configurations
.Description
  Idempotently applies New Relic synthetic monitor configurations. The monitor configuration determines what sites are being monitored and from which locations.
.Example
  Set-SyntheticConditionMonitor -AdminAPIKey $AdminAPIKey -DefinedSytheticMonitors $Config.SyntheticMonitors
  Uses New Relic APIs to update synthetic monitors to match the configuration defined in $Config.SyntheticMonitors. Any existing monitors that are not defined will be removed.
.Parameter AdminAPIAPIKey
  Must be an admin user's API Key, not an account-level REST API Key. See more here: https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys
.Parameter DefinedSyntheticMonitors
  An array of monitor objects which define the desired configuration state for New Relic synthetic monitors
#>

Function Set-NRSyntheticLocationMonitorConfiguration {
  [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", '', Justification = "All CMDLets being called have ShouldProcess in place and the preference is passed to them." )]
  [CMDLetBinding(SupportsShouldProcess = $true)]
  Param (
    [Parameter (Mandatory = $true)]
    [string] $AdminAPIKey,
    [array] $DefinedSyntheticMonitors
  )

  # Iterate over each defined monitor
  [System.Collections.ArrayList]$existingMonitors = @(Get-NRSyntheticMonitor -APIKey $AdminAPIKey)
  Write-Verbose "There are currently $($DefinedSyntheticMonitors.count) defined Synthetic monitors and $($existingMonitors.count) existing Synthetic monitors."
  Foreach ($monitor in $DefinedSyntheticMonitors) {
    [array]$existingMonitor = $existingMonitors | Where-Object {$_.name -eq $monitor.name}

    # Items are declared by name and are later given an ID by New Relic, therefore resources with the same name will cause issues and are unsupported.
    If ($existingMonitor.count -gt 1) {
      Write-Error "Multiple monitors found with the name $($existingMonitor.name[0])!
      Ids: $($existingMonitor.id -join (','))
      This must be resolved manually before continuing."
 -ErrorAction 'Stop'
    }

      # Update existing condition if needed
      If ($existingMonitor) {
        $monitorRequiresUpdate = Compare-NRSyntheticMonitorState -DefinedMonitor $monitor -ExistingMonitor $existingMonitor -Verbose:$PSCMDLet.GetVariableValue('VerbosePreference')

        If ($monitorRequiresUpdate) {
            Write-Verbose "Updating monitor $($monitor.name)"

            # Remove type property from monitor and pipe all other properties into the CMDLet
            $monitorParams = [pscustomobject] ($monitor | Select-Object -ExcludeProperty 'type')
            $monitorParams | Update-NRSyntheticMonitor -APIKey $AdminAPIKey -ID $existingMonitor.Id -Whatif:$WhatIfPreference | Out-Null
        }

        # Any extra existing monitors that are not defined will be removed later
        $existingMonitors.Remove($existingMonitor[0])
      }

      # Otherwise, create monitor
      Else {
        Write-Verbose "Creating monitor $($monitor.name)"
        New-NRSyntheticMonitor @monitor -APIKey $AdminAPIKey -Whatif:$WhatIfPreference | Out-Null
      }
    }

    # Check for existing monitors not in the definition and remove them
    # Note that the Synthetic API returns an object with empty properties rather than null when no monitors exist so check using the id property
    If ($existingMonitors.id) {
      Write-Verbose "Removing monitor(s) $($existingMonitors.Name -join (','))"
      $existingMonitors.Id | Remove-NRSyntheticMonitor $AdminAPIKey  -Whatif:$WhatIfPreference | Out-Null
    }
}

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

Function Compare-NRSyntheticMonitorState {
  [CMDLetBinding()]
  Param (
    $DefinedMonitor,
    $ExistingMonitor
  )
  [boolean]$returnValue = $false
  $KeysExcludedFromComparison = @('Type', 'ScriptVariables')
  Foreach ($property in ($DefinedMonitor.Keys | Where-Object {$_ -notin $KeysExcludedFromComparison})) {

    # Options Requires special handling
    $optionsProperties = @('validatioString','verifySSL','bypassHEADRequest','treatRedirectAsFailure')
    If ($property -in $optionsProperties) {
      If ($DefinedMonitor.$property -ne $ExistingMonitor.options.$property) {
        Write-Verbose "Monitor $($DefinedMonitor.Name) has options property $property defined as $($DefinedMonitor.$property) but it is currently set to $($ExistingMonitor.options.$property)"
        $returnValue = $true
      }
    }

    # Locations also require special handling since this is an array of items
    ElseIf ($property -eq 'locations') {
      $locationsDiff = Compare-Object -ReferenceObject $DefinedMonitor.$property -DifferenceObject $ExistingMonitor.$property -PassThru
      If ($locationsDiff) {
        Write-Verbose "Monitor $($DefinedMonitor.Name) has an locations list of $($DefinedMonitor.$property) but it is currently set to $($ExistingMonitor.$property)"
        $returnValue = $true
      }
    }

    # Script path requires special handling to evaluate if update is needed
    ElseIf ($property -eq 'scriptPath') {
      If (Test-Path $DefinedMonitor.$property) {

        # Replace variables on script and convert to base64 then compare against existing script content
        $newScriptContent = Get-NRScriptContent -ScriptPath $DefinedMonitor.$property -ScriptVariables $DefinedMonitor.ScriptVariables
        $existingScriptContent = (Get-NRSyntheticMonitorScript -APIKey $AdminAPIKey -Id $ExistingMonitor.id).scriptText

        If ($newScriptContent -ne $existingScriptContent) {
          Write-Verbose "Monitor $($DefinedMonitor.Name) script's content has changed"
          $returnValue = $true
        }
      } Else {
        Write-Error "Cannot find script for $($DefinedMonitor.Name) at $($DefinedMonitor.$property)"
      }
    }

    # Compare top-level properties
    ElseIf ($DefinedMonitor.$property -ne $ExistingMonitor.$property) {
      Write-Verbose "Monitor $($DefinedMonitor.Name) has property $property defined as $($DefinedMonitor.$property) but it is currently set to $($ExistingMonitor.$property)"
      $returnValue = $true
    }
  }
  Return $returnValue
}