NewRelicPS.Configuration.NRQLCondition.psm1

<#
.Synopsis
  Idempotently applies New Relic condition configurations
.Description
  Idempotently applies New Relic condition configurations
.Example
  Set-NRQLConditionConfiguration -AdminAPIKey $AdminAPIKey -PersonalAPIKey $PersonalAPIKey -DefinedPolicies $Config.AlertPolicies -AccountId $AccountId
  Uses New Relic APIs to update alert conditions to match the conditions defined in $Config.AlertPolicies. Any existing conditions that are not defined will be removed.
.Parameter AccountId
  The New Relic account Id where the configuration is being applied.
.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 PersonalAPIKey
  Must be a personal 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 DefinedPolicies
  An array of policy objects which define the desired configuration state for New Relic alert policies and conditions
#>

Function Set-NRQLConditionConfiguration {
  [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,
    [Parameter (Mandatory = $true)]
    [string] $PersonalAPIKey,
    [Parameter (Mandatory = $true)]
    [string] $AccountId,
    [Parameter (Mandatory = $false)]
    [array] $DefinedPolicies
  )

  # Conditions are described in a policy, but the API only returns the link in the notification channel
  $existingPolicies = Get-NRAlertPolicy -APIKey $AdminAPIKey
  $NRQLTypes = @('Static','Baseline','Outlier')
  Foreach ($policy in $DefinedPolicies) {
    $existingPolicy = $existingPolicies | Where-Object {$_.name -eq $policy.name}
    [System.Collections.ArrayList]$existingConditions = @(Get-NRQLCondition -APIKey $PersonalAPIKey -AccountId $AccountId -PolicyId $existingPolicy.id)
    Write-Verbose "Policy $($policy.name) has $(($policy.conditions | Where-Object {$_.type -in $NRQLTypes}).name.count) defined NRQL conditions and $($existingConditions.count) existing NRQL conditions."

    # Iterate over each NRQL condition described in the policy
    Foreach ($condition in ($policy.conditions | Where-Object {$NRQLTypes -contains $_.type})) {
      [array]$existingConditionConfig = $existingConditions | Where-Object { $_.name -eq $condition.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 ($existingConditionConfig.count -gt 1) {
        Write-Error "Multiple conditions found with the name $($existingConditionConfig.name[0])!
        Ids: $($existingConditionConfig.id -join (','))
        This must be resolved manually before continuing."
 -ErrorAction 'Stop'
      }

      # Update existing condition if needed
      If ($existingConditionConfig) {
        $conditionRequiresUpdate = Compare-NRConditionState -DefinedCondition $condition -ExistingCondition $existingConditionConfig -Verbose:$PSCMDLet.GetVariableValue('VerbosePreference')

        If ($conditionRequiresUpdate) {
            Write-Verbose "Updating condition $($condition.name) on policy $($policy.name)."
            $params = Get-NRQLConditionParams -APIKey $PersonalAPIKEY -Condition $condition -AccountId $AccountId
            Update-NRQLCondition @params -ConditionId $existingConditionConfig.id -Whatif:$WhatIfPreference | Out-Null
        }

        # Any extra existing channels that are not defined will be removed later
        $existingConditions.Remove($existingConditionConfig[0])
      }

      # Otherwise, create the condition if the policy exists
      ElseIf ($existingPolicy) {
        $params = Get-NRQLConditionParams -APIKey $PersonalAPIKEY -Condition $condition -AccountId $AccountId
        Write-Verbose "Creating condition $($condition.name)"
        New-NRQLCondition @params -PolicyId $existingPolicy.id -Whatif:$WhatIfPreference | Out-Null
      }
    }

    # Check for existing conditions not in the policy's definition and remove them
    If ($existingConditions) {
      Write-Verbose "Removing condition(s) $($existingConditions.Name -join (','))"
      $existingConditions.Id | Remove-NRCondition $PersonalAPIKey -AccountId $AccountId -Whatif:$WhatIfPreference | Out-Null
    }
  }
}

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

Function Get-NRQLConditionParams {
  [CMDLetBinding()]
  param (
    [Parameter (Mandatory = $true)]
    $APIKey,
    [Parameter (Mandatory = $true)]
    $AccountId,
    [Parameter (Mandatory = $true)]
    $Condition
  )

  # Take the data available and create a hash table of CMDLET parameters
  $params = @{
    APIKey            = $APIKEY
    AccountId         = $AccountId
  }

  # Build up all optional parameters
  Foreach ($Property in $Condition.Keys) {

    # There are a few possibilities for nested properties, so address those first
    $NestedProperties = ('nrql','expiration')
    If ($NestedProperties -contains $Property) {
      Foreach ($subProperty in $Condition.$Property.Keys) {
        $params += @{
          $subProperty = $Condition.$Property.$subProperty
        }
      }
    }

    # Terms require special handling
    ElseIf ($Property -eq 'terms') {
      Foreach ($term in $Condition.$Property) {
        If ($term.priority -eq 'Critical') {
          Switch ($term.keys) {
            'operator' {$params += @{CriticalOperator = $term.operator}}
            'threshold' {$params += @{CriticalThreshold = $term.threshold}}
            'thresholdDuration' {$params += @{CriticalDuration = $term.thresholdDuration}}
            'thresholdOccurrences' {$params += @{CriticalOccurrences = $term.thresholdOccurrences}}
          }
        }
        ElseIf ($term.priority -eq 'Warning') {
          Switch ($term.keys) {
            'operator' {$params += @{WarningOperator = $term.operator}}
            'threshold' {$params += @{WarningThreshold = $term.threshold}}
            'thresholdDuration' {$params += @{WarningDuration = $term.thresholdDuration}}
            'thresholdOccurrences' {$params += @{WarningOccurrences = $term.thresholdOccurrences}}
          }
        }
        Else {
          Write-Error "$($Condition.name) has terms defined but a priority was not found!" -ErrorAction 'Stop'
        }
      }
    }

    # Process the top-level properties
    Else {
      $params +=  @{
        $Property = $Condition.$Property
      }
    }
  }
  Return $params
}

Function Compare-NRConditionState {
  [CMDLetBinding()]
  Param (
    $DefinedCondition,
    $ExistingCondition
  )
  $returnValue = $false
  $NestedProperties = ('nrql','expiration')
  Foreach ($Property in $DefinedCondition.Keys) {

    # Terms require special handling since there may be multiple term objects returned in any order
    If ($Property -eq 'terms') {
      Foreach ($term in $DefinedCondition.$Property) {

        # There can only be one Warning term and one Critical term so match the term based on priority
        $existingTerm = $ExistingCondition.$Property | Where-Object {$_.priority -eq $term.priority}

        # Iterate over sub items in terms and compare them
        Foreach ($subProperty in $term.keys) {
          If ($term.$subProperty -ne $existingTerm.$subProperty) {
            Write-Verbose "Condition $($DefinedCondition.Name) has subproperty $subProperty defined as $($term.$subProperty) but it is currently set to $($Existingterm.$subProperty)"
            $returnValue = $true
          }
        }
      }
    }

    # There are a few possibilities for nested properties, so address those first
    ElseIf ($NestedProperties -contains $Property) {
      Foreach ($subProperty in $DefinedCondition.$Property.Keys) {
        If ($DefinedCondition.$Property.$subProperty -ne $ExistingCondition.$Property.$subProperty) {
          Write-Verbose "Condition $($DefinedCondition.Name) has subproperty $subProperty defined as $($DefinedCondition.$Property.$subProperty) but it is currently set to $($ExistingCondition.$Property.$subProperty)"
          $returnValue = $true
        }
      }
    }
    ElseIf ($DefinedCondition.$Property -ne $ExistingCondition.$Property) {
      Write-Verbose "Condition $($DefinedCondition.Name) has property $Property defined as $($DefinedCondition.$Property) but it is currently set to $($ExistingCondition.$Property)"
      $returnValue = $true
    }
  }
  Return $returnValue
}