This PR adds filtering capabilities to the PagerDuty migrator tool and fixes user notification rule preservation behavior. Closes https://github.com/grafana/irm/issues/612 ## Changes ### 1. Added Resource Filtering Added the ability to filter PagerDuty resources during migration based on: - Team membership - User association - Name patterns (using regex) New environment variables for filtering: ``` PAGERDUTY_FILTER_TEAM PAGERDUTY_FILTER_USERS PAGERDUTY_FILTER_SCHEDULE_REGEX PAGERDUTY_FILTER_ESCALATION_POLICY_REGEX PAGERDUTY_FILTER_INTEGRATION_REGEX ``` #### Example Usage Filter by team: ```bash docker run --rm \ -e MIGRATING_FROM="pagerduty" \ -e MODE="plan" \ -e ONCALL_API_URL="<your-oncall-api-url>" \ -e ONCALL_API_TOKEN="<your-oncall-api-token>" \ -e PAGERDUTY_API_TOKEN="<your-pd-api-token>" \ -e PAGERDUTY_FILTER_TEAM="SRE Team" \ oncall-migrator ``` Filter by specific users: ```bash docker run --rm \ -e MIGRATING_FROM="pagerduty" \ -e MODE="plan" \ -e ONCALL_API_URL="<your-oncall-api-url>" \ -e ONCALL_API_TOKEN="<your-oncall-api-token>" \ -e PAGERDUTY_API_TOKEN="<your-pd-api-token>" \ -e PAGERDUTY_FILTER_USERS="P123ABC,P456DEF" \ oncall-migrator ``` Filter schedules by name pattern: ```bash docker run --rm \ -e MIGRATING_FROM="pagerduty" \ -e MODE="plan" \ -e ONCALL_API_URL="<your-oncall-api-url>" \ -e ONCALL_API_TOKEN="<your-oncall-api-token>" \ -e PAGERDUTY_API_TOKEN="<your-pd-api-token>" \ -e PAGERDUTY_FILTER_SCHEDULE_REGEX="^(Primary|Secondary)" \ oncall-migrator ``` Filter escalation policies by name pattern: ```bash docker run --rm \ -e MIGRATING_FROM="pagerduty" \ -e MODE="plan" \ -e ONCALL_API_URL="<your-oncall-api-url>" \ -e ONCALL_API_TOKEN="<your-oncall-api-token>" \ -e PAGERDUTY_API_TOKEN="<your-pd-api-token>" \ -e PAGERDUTY_FILTER_ESCALATION_POLICY_REGEX="^Prod" \ oncall-migrator ``` Filter integrations by name pattern: ```bash docker run --rm \ -e MIGRATING_FROM="pagerduty" \ -e MODE="plan" \ -e ONCALL_API_URL="<your-oncall-api-url>" \ -e ONCALL_API_TOKEN="<your-oncall-api-token>" \ -e PAGERDUTY_API_TOKEN="<your-pd-api-token>" \ -e PAGERDUTY_FILTER_INTEGRATION_REGEX="Prometheus$" \ oncall-migrator ``` ### 2. Fixed User Notification Rule Preservation Introduces a `PRESERVE_EXISTING_USER_NOTIFICATION_RULES` config (default of `true`). The migrator now: - does not delete user notification rules in Grafana OnCall, if the Grafana user already has some defined, AND `PRESERVE_EXISTING_USER_NOTIFICATION_RULES` is True - if the Grafana user has no personal notification rules defined in OnCall, we will create them - deletes existing user notification rules, and creates new ones, in Grafana OnCall, if `PRESERVE_EXISTING_USER_NOTIFICATION_RULES` is False - basically make sure that the state in Grafana OnCall matches the _latest_ state in PagerDuty - Improves logging to clearly indicate when rules are being preserved #### Example Usage Preserve existing notification policies (default): ```bash docker run --rm \ -e MIGRATING_FROM="pagerduty" \ -e MODE="migrate" \ -e ONCALL_API_URL="<your-oncall-api-url>" \ -e ONCALL_API_TOKEN="<your-oncall-api-token>" \ -e PAGERDUTY_API_TOKEN="<your-pd-api-token>" \ oncall-migrator ``` Replace existing notification policies: ```bash docker run --rm \ -e MIGRATING_FROM="pagerduty" \ -e MODE="migrate" \ -e ONCALL_API_URL="<your-oncall-api-url>" \ -e ONCALL_API_TOKEN="<your-oncall-api-token>" \ -e PAGERDUTY_API_TOKEN="<your-pd-api-token>" \ -e PRESERVE_EXISTING_USER_NOTIFICATION_RULES="false" \ oncall-migrator ``` ### 3. Improved Testing Added comprehensive test coverage for filtering functionality and updated user notification rule preservation tests ## Testing Done - Manual testing of filtering capabilities in both plan and migrate modes - Verified notification policy preservation behavior
103 lines
3.2 KiB
Python
103 lines
3.2 KiB
Python
import copy
|
|
|
|
from lib.oncall.api_client import OnCallAPIClient
|
|
from lib.pagerduty.config import (
|
|
PAGERDUTY_TO_ONCALL_CONTACT_METHOD_MAP,
|
|
PRESERVE_EXISTING_USER_NOTIFICATION_RULES,
|
|
)
|
|
from lib.utils import remove_duplicates, transform_wait_delay
|
|
|
|
|
|
def remove_duplicate_rules_between_waits(rules: list[dict]) -> list[dict]:
|
|
"""
|
|
Remove duplicate rules in chunks between wait rules.
|
|
E.g. "SMS - SMS - 1min - Phone call" becomes "SMS - 1min - Phone call"
|
|
"""
|
|
rules_copy = copy.deepcopy(rules)
|
|
|
|
for method in set(PAGERDUTY_TO_ONCALL_CONTACT_METHOD_MAP.values()):
|
|
rules_copy = remove_duplicates(
|
|
rules_copy,
|
|
split_condition=lambda rule: rule["type"] == "wait",
|
|
duplicate_condition=lambda rule: rule["type"] == method,
|
|
)
|
|
|
|
return rules_copy
|
|
|
|
|
|
def migrate_notification_rules(user: dict) -> None:
|
|
if (
|
|
PRESERVE_EXISTING_USER_NOTIFICATION_RULES
|
|
and user["oncall_user"]["notification_rules"]
|
|
):
|
|
print(f"Preserving existing notification rules for {user['email']}")
|
|
return
|
|
|
|
notification_rules = [
|
|
rule for rule in user["notification_rules"] if rule["urgency"] == "high"
|
|
]
|
|
|
|
for important in (False, True):
|
|
oncall_rules = transform_notification_rules(
|
|
notification_rules, user["oncall_user"]["id"], important
|
|
)
|
|
|
|
for rule in oncall_rules:
|
|
OnCallAPIClient.create("personal_notification_rules", rule)
|
|
|
|
if oncall_rules:
|
|
# delete old notification rules if any new rules were created
|
|
for rule in user["oncall_user"]["notification_rules"]:
|
|
if rule["important"] == important:
|
|
OnCallAPIClient.delete(
|
|
"personal_notification_rules/{}".format(rule["id"])
|
|
)
|
|
|
|
|
|
def transform_notification_rules(
|
|
notification_rules: list[dict], user_id: str, important: bool
|
|
) -> list[dict]:
|
|
"""
|
|
Transform PagerDuty user notification rules to Grafana OnCall personal notification rules.
|
|
"""
|
|
notification_rules = sorted(
|
|
notification_rules, key=lambda rule: rule["start_delay_in_minutes"]
|
|
)
|
|
|
|
oncall_notification_rules = []
|
|
for idx, rule in enumerate(notification_rules):
|
|
delay = rule["start_delay_in_minutes"]
|
|
|
|
if idx > 0:
|
|
previous_delay = notification_rules[idx - 1]["start_delay_in_minutes"]
|
|
delay -= previous_delay
|
|
|
|
oncall_notification_rules += transform_notification_rule(
|
|
rule, delay, user_id, important
|
|
)
|
|
|
|
oncall_notification_rules = remove_duplicate_rules_between_waits(
|
|
oncall_notification_rules
|
|
)
|
|
|
|
return oncall_notification_rules
|
|
|
|
|
|
def transform_notification_rule(
|
|
notification_rule: dict, delay: int, user_id: str, important: bool
|
|
) -> list[dict]:
|
|
contact_method_type = notification_rule["contact_method"]["type"]
|
|
oncall_type = PAGERDUTY_TO_ONCALL_CONTACT_METHOD_MAP[contact_method_type]
|
|
|
|
notify_rule = {"user_id": user_id, "type": oncall_type, "important": important}
|
|
|
|
if not delay:
|
|
return [notify_rule]
|
|
|
|
wait_rule = {
|
|
"user_id": user_id,
|
|
"type": "wait",
|
|
"duration": transform_wait_delay(delay),
|
|
"important": important,
|
|
}
|
|
return [wait_rule, notify_rule]
|