Skip to content

feat(pagerduty/alert): support integration with pagerduty#400

Open
v1v wants to merge 7 commits intomainfrom
feature/support-pagerduty
Open

feat(pagerduty/alert): support integration with pagerduty#400
v1v wants to merge 7 commits intomainfrom
feature/support-pagerduty

Conversation

@v1v
Copy link
Member

@v1v v1v commented Nov 5, 2025

See https://developer.pagerduty.com/api-reference/368ae3d938c9e-send-an-event-to-pager-duty

Initially implemented at https://github.com/elastic/app-obs-dev/tree/main/.github/actions/pagerduty-alert

But decided to create a fork so we can keep its maintenance outside of the SDH automation

It worked fine

image

Copilot AI review requested due to automatic review settings November 5, 2025 16:42
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new GitHub composite action for integrating with PagerDuty's alert system. The action allows triggering PagerDuty incidents and retrieving the resulting incident URL.

Key Changes:

  • Adds a new pagerduty/alert composite action that sends events to PagerDuty's v2 Events API
  • Implements incident creation with configurable summary, source, severity, component, and routing
  • Includes functionality to fetch and return the created incident's URL

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
pagerduty/alert/action.yml Defines the composite action with inputs, outputs, and steps for triggering alerts and fetching incident URLs
pagerduty/alert/README.md Provides documentation including usage examples and input/output descriptions
.github/workflows/test-pagerduty-alert.yml Adds automated testing workflow for the new PagerDuty alert action
.github/workflows/no-test.yml Excludes the pagerduty/alert path from the no-test workflow

@v1v v1v added the changelog:feature When you add a new feature label Nov 5, 2025
@v1v v1v self-assigned this Nov 5, 2025
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings November 5, 2025 16:44
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

id: pagerduty_incident
uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5
with:
url: 'https://api.pagerduty.com/incidents?since=${{steps.sanitize.outputs.time-search}}&statuses[]=triggered&statuses[]=acknowledged'
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fetching all incidents from the last 15 minutes without pagination could be inefficient if there are many incidents. Consider adding pagination support or filtering by additional parameters to reduce response size.

Suggested change
url: 'https://api.pagerduty.com/incidents?since=${{steps.sanitize.outputs.time-search}}&statuses[]=triggered&statuses[]=acknowledged'
url: 'https://api.pagerduty.com/incidents?since=${{steps.sanitize.outputs.time-search}}&statuses[]=triggered&statuses[]=acknowledged&limit=20'

Copilot uses AI. Check for mistakes.
with:
script: |
const responseData = `${{ steps.pagerduty_incident.outputs.response }}`
const parsedData = JSON.parse(responseData)
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parsing the response data without error handling could cause the action to fail silently if the API returns invalid JSON or the response is unexpectedly structured. Add try-catch error handling to provide meaningful error messages.

Suggested change
const parsedData = JSON.parse(responseData)
let parsedData;
try {
parsedData = JSON.parse(responseData)
} catch (error) {
console.error('Failed to parse PagerDuty incident response as JSON:', error)
throw new Error('Invalid JSON response from PagerDuty incidents API')
}

Copilot uses AI. Check for mistakes.
const responseData = `${{ steps.pagerduty_incident.outputs.response }}`
const parsedData = JSON.parse(responseData)
const summary = process.env.SUMMARY
const specificIncident = parsedData.incidents.find(incident => incident.title === summary)
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The search assumes 'parsedData.incidents' exists and is an array. If the PagerDuty API returns an error or unexpected structure, this will throw an error. Add validation to check if 'incidents' exists before accessing it.

Suggested change
const specificIncident = parsedData.incidents.find(incident => incident.title === summary)
let specificIncident = null;
if (parsedData && Array.isArray(parsedData.incidents)) {
specificIncident = parsedData.incidents.find(incident => incident.title === summary)
} else {
console.log('No incidents array found in PagerDuty response.');
}

Copilot uses AI. Check for mistakes.
@v1v v1v marked this pull request as ready for review November 5, 2025 16:47
@v1v v1v requested a review from a team as a code owner November 5, 2025 16:47
Copilot AI review requested due to automatic review settings November 5, 2025 16:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

id: pagerduty_incident
uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5
with:
url: 'https://api.pagerduty.com/incidents?since=${{steps.sanitize.outputs.time-search}}&statuses[]=triggered&statuses[]=acknowledged'
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a potential race condition: the incident may not be immediately available when fetching. Consider adding retry logic or a small delay between triggering the alert (line 47) and fetching incidents (line 55).

Copilot uses AI. Check for mistakes.
@v1v v1v requested a review from amannocci November 5, 2025 16:48
core.setOutput("time-search", isoDate)

- name: Trigger PagerDuty Alert
uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because this is a copy of an existing composite action - so I prefered to keep the same implementation from now

Copy link
Member Author

@v1v v1v Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings November 11, 2025 14:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

with:
url: 'https://events.pagerduty.com/v2/enqueue'
method: 'POST'
customHeaders: '{"Accept": "application/json", "Content-Type": "application/json", "Authorization" : "Token token=${{ inputs.api-key }}"}'
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API key is exposed in plain text in the workflow logs via customHeaders. Consider using a masked approach or ensure that the http-request-action properly masks sensitive headers to prevent accidental exposure in logs.

Copilot uses AI. Check for mistakes.
Comment on lines +72 to +74
const responseData = `${{ steps.pagerduty_incident.outputs.response }}`
const parsedData = JSON.parse(responseData)
const summary = process.env.SUMMARY
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using template literals to embed GitHub Actions output can break if the response contains backticks or special characters. Use fromJSON directly: const parsedData = JSON.parse('${{ steps.pagerduty_incident.outputs.response }}')

Suggested change
const responseData = `${{ steps.pagerduty_incident.outputs.response }}`
const parsedData = JSON.parse(responseData)
const summary = process.env.SUMMARY
const parsedData = ${{ fromJSON(steps.pagerduty_incident.outputs.response) }}
const summary = process.env.SUMMARY

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings November 11, 2025 15:06
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

url: 'https://events.pagerduty.com/v2/enqueue'
method: 'POST'
customHeaders: '{"Accept": "application/json", "Content-Type": "application/json", "Authorization" : "Token token=${{ inputs.api-key }}"}'
data: '{"event_action": "trigger", "routing_key": "${{ inputs.routing-key }}", "payload": {"summary": "${{steps.sanitize.outputs.summary}}", "source": "${{ inputs.source }}", "custom_details":"${{ inputs.source }}", "severity": "${{ inputs.severity }}", "component": "${{ inputs.component }}"}}'
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The custom_details field is set to the same value as source (${{ inputs.source }}), which appears to be unintentional. According to PagerDuty API documentation, custom_details should contain additional context as an object, not duplicate the source string.

Suggested change
data: '{"event_action": "trigger", "routing_key": "${{ inputs.routing-key }}", "payload": {"summary": "${{steps.sanitize.outputs.summary}}", "source": "${{ inputs.source }}", "custom_details":"${{ inputs.source }}", "severity": "${{ inputs.severity }}", "component": "${{ inputs.component }}"}}'
data: '{"event_action": "trigger", "routing_key": "${{ inputs.routing-key }}", "payload": {"summary": "${{steps.sanitize.outputs.summary}}", "source": "${{ inputs.source }}", "custom_details": {"source": "${{ inputs.source }}", "component": "${{ inputs.component }}", "summary": "${{ steps.sanitize.outputs.summary }}"}, "severity": "${{ inputs.severity }}", "component": "${{ inputs.component }}"}}'

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +81
- name: Fetch PagerDuty Incidents
id: pagerduty_incident
uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5
with:
url: 'https://api.pagerduty.com/incidents?since=${{steps.sanitize.outputs.time-search}}&statuses[]=triggered&statuses[]=acknowledged'
method: 'GET'
customHeaders: '{"Accept": "application/json", "Content-Type": "application/json", "Authorization" : "Token token=${{ inputs.api-key }}"}'

- name: Search the incident
uses: actions/github-script@v8
id: get_incident
env:
SUMMARY: ${{ steps.sanitize.outputs.summary }}
with:
script: |
const responseData = `${{ steps.pagerduty_incident.outputs.response }}`
const parsedData = JSON.parse(responseData)
const summary = process.env.SUMMARY
const specificIncident = parsedData.incidents.find(incident => incident.title === summary)
if (specificIncident) {
console.log(`Found HTML URL: ${specificIncident.html_url}`)
return specificIncident.html_url
} else {
console.log('No incident found.')
return ''
}
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition: The incident search queries PagerDuty immediately after triggering the alert (lines 56-62), but PagerDuty may not have processed and indexed the incident yet. Consider adding a retry mechanism with exponential backoff or a brief delay before searching.

Suggested change
- name: Fetch PagerDuty Incidents
id: pagerduty_incident
uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5
with:
url: 'https://api.pagerduty.com/incidents?since=${{steps.sanitize.outputs.time-search}}&statuses[]=triggered&statuses[]=acknowledged'
method: 'GET'
customHeaders: '{"Accept": "application/json", "Content-Type": "application/json", "Authorization" : "Token token=${{ inputs.api-key }}"}'
- name: Search the incident
uses: actions/github-script@v8
id: get_incident
env:
SUMMARY: ${{ steps.sanitize.outputs.summary }}
with:
script: |
const responseData = `${{ steps.pagerduty_incident.outputs.response }}`
const parsedData = JSON.parse(responseData)
const summary = process.env.SUMMARY
const specificIncident = parsedData.incidents.find(incident => incident.title === summary)
if (specificIncident) {
console.log(`Found HTML URL: ${specificIncident.html_url}`)
return specificIncident.html_url
} else {
console.log('No incident found.')
return ''
}
# Removed "Fetch PagerDuty Incidents" step; incident fetching and retry logic moved to next step.
- name: Search the incident with retry
uses: actions/github-script@v8
id: get_incident
env:
SUMMARY: ${{ steps.sanitize.outputs.summary }}
API_KEY: ${{ inputs.api-key }}
TIME_SEARCH: ${{ steps.sanitize.outputs.time-search }}
with:
script: |
const fetch = require('node-fetch');
const summary = process.env.SUMMARY;
const apiKey = process.env.API_KEY;
const timeSearch = process.env.TIME_SEARCH;
const maxAttempts = 5;
let attempt = 0;
let delay = 1000; // start with 1s
let specificIncident = null;
const url = `https://api.pagerduty.com/incidents?since=${encodeURIComponent(timeSearch)}&statuses[]=triggered&statuses[]=acknowledged`;
const headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": `Token token=${apiKey}`
};
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function findIncident() {
for (attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const res = await fetch(url, { method: 'GET', headers });
if (!res.ok) {
throw new Error(`PagerDuty API error: ${res.status} ${res.statusText}`);
}
const parsedData = await res.json();
specificIncident = parsedData.incidents.find(incident => incident.title === summary);
if (specificIncident) {
console.log(`Found HTML URL: ${specificIncident.html_url} on attempt ${attempt}`);
return specificIncident.html_url;
} else {
console.log(`Attempt ${attempt}: Incident not found, retrying after ${delay}ms...`);
await sleep(delay);
delay *= 2; // exponential backoff
}
} catch (err) {
console.log(`Attempt ${attempt}: Error fetching incidents: ${err.message}`);
await sleep(delay);
delay *= 2;
}
}
console.log('No incident found after retries.');
return '';
}
return await findIncident();

Copilot uses AI. Check for mistakes.
id: pagerduty_incident
uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5
with:
url: 'https://api.pagerduty.com/incidents?since=${{steps.sanitize.outputs.time-search}}&statuses[]=triggered&statuses[]=acknowledged'
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The incident search retrieves all triggered and acknowledged incidents from the last 15 minutes without pagination limits. In high-traffic environments, this could return many incidents. Consider adding &limit=100 parameter to bound the response size.

Suggested change
url: 'https://api.pagerduty.com/incidents?since=${{steps.sanitize.outputs.time-search}}&statuses[]=triggered&statuses[]=acknowledged'
url: 'https://api.pagerduty.com/incidents?since=${{steps.sanitize.outputs.time-search}}&statuses[]=triggered&statuses[]=acknowledged&limit=100'

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog:feature When you add a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants