diff --git a/.github/workflows/no-test.yml b/.github/workflows/no-test.yml index 1dd85adb..9e553456 100644 --- a/.github/workflows/no-test.yml +++ b/.github/workflows/no-test.yml @@ -27,6 +27,7 @@ on: - '!oblt-cli/cluster-name-validation/**' - '!oblt-cli/list/**' - '!oblt-cli/run/**' + - '!pagerduty/alert/**' - '!pre-commit/**' - '!updatecli/run/**' diff --git a/.github/workflows/test-pagerduty-alert.yml b/.github/workflows/test-pagerduty-alert.yml new file mode 100644 index 00000000..ea49ed2a --- /dev/null +++ b/.github/workflows/test-pagerduty-alert.yml @@ -0,0 +1,35 @@ +name: test-pagerduty-alert + +on: + merge_group: ~ + workflow_dispatch: ~ + pull_request: + branches: + - main + paths: + - '.github/workflows/test-pagerduty-alert.yml' + - 'pagerduty/alert/**' + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: ./pagerduty/alert + if: github.event_name == 'pull_request' + id: pagerduty-alert + with: + summary: 'Test Alert from OBLT Actions' + source: 'OBLT Actions Test Suite' + api-key: ${{ secrets.PD_SECRET }} + component: 'OBLT Actions' + routing-key: ${{ secrets.PD_ROUTING_KEY }} + severity: 'critical' + + - name: Assert output is not empty + if: github.event_name == 'pull_request' + run: | + [ -n "${{ steps.pagerduty-alert.outputs.incident-url }}" ] diff --git a/pagerduty/alert/README.md b/pagerduty/alert/README.md new file mode 100644 index 00000000..2a4aa42b --- /dev/null +++ b/pagerduty/alert/README.md @@ -0,0 +1,51 @@ +# pagerduty-alert + +[![usages](https://img.shields.io/badge/usages-white?logo=githubactions&logoColor=blue)](https://github.com/search?q=elastic%2Foblt-actions%2Fpagerduty%2Falert+%28path%3A.github%2Fworkflows+OR+path%3A**%2Faction.yml+OR+path%3A**%2Faction.yaml%29&type=code) +[![test-pagerduty-alert](https://github.com/elastic/oblt-actions/actions/workflows/test-pagerduty-alert.yml/badge.svg?branch=main)](https://github.com/elastic/oblt-actions/actions/workflows/test-pagerduty-alert.yml) + + +Raise a PagerDuty alert and return the incident URL + + +## Inputs + +| Name | Description | Required | Default | +|---------------|------------------------------------|----------|------------| +| `summary` | The PagerDuty summary of the alert | `true` | ` ` | +| `source` | The PagerDuty source of the alert | `true` | ` ` | +| `api-key` | The PagerDuty API key | `true` | ` ` | +| `component` | The PagerDuty component | `true` | ` ` | +| `routing-key` | The PagerDuty integration key | `true` | ` ` | +| `severity` | The PagerDuty severity | `false` | `critical` | + + +## Outputs + + +| Name | Description | +|----------------|----------------------------------------| +| `incident-url` | The HTML URL of the PagerDuty incident | + + +## Usage + +```yaml +jobs: + assign-engineer-urgent-now: + steps: + - uses: elastic/oblt-actions/pagerduty/alert@v1 + id: pagerduty + with: + summary: "Reported some errors with XYZ" + source: "https://..." + api-key: "${{ secrets.PD_SECRET }}" + component: "my-component" + severity: "critical" + routing-key: "${{ secrets.PD_ROUTING_KEY }}" + + - name: Notify a pagerduty incident has been created + run: echo "${{steps.pagerduty.outputs.incident-url}} has been created" + +``` + + diff --git a/pagerduty/alert/action.yml b/pagerduty/alert/action.yml new file mode 100644 index 00000000..b1b72d7d --- /dev/null +++ b/pagerduty/alert/action.yml @@ -0,0 +1,82 @@ +name: 'pagerduty-alert' +description: 'Raise a PagerDuty alert and return the incident URL' +inputs: + summary: + description: 'The PagerDuty summary of the alert' + required: true + source: + description: 'The PagerDuty source of the alert' + required: true + api-key: + description: 'The PagerDuty API key' + required: true + component: + description: 'The PagerDuty component' + required: true + routing-key: + description: 'The PagerDuty integration key' + required: true + severity: + description: 'The PagerDuty severity' + default: 'critical' + required: false + +outputs: + incident-url: + description: 'The HTML URL of the PagerDuty incident' + value: ${{ steps.get_incident.outputs.result }} + +runs: + using: "composite" + steps: + - uses: actions/github-script@v8 + id: sanitize + env: + SUMMARY: ${{ inputs.summary }} + with: + script: | + const sanitizedTitle = JSON.stringify(process.env.SUMMARY).slice(1, -1) + console.log(`sanitized summary is: ${sanitizedTitle}`) + core.setOutput("summary", sanitizedTitle) + + // Time window (15 minutes) for searching recent incidents, in milliseconds + const now = new Date() + const fifteenMinutesAgo = new Date(now.getTime() - (15 * 60 * 1000)) + const isoDate = fifteenMinutesAgo.toISOString() + core.setOutput("time-search", isoDate) + + - name: Trigger PagerDuty Alert + uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5 + with: + 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 }}"}}' + + - 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 '' + } + result-encoding: string