From 684b222d8a1e763ae3d48e068802ef028d03e751 Mon Sep 17 00:00:00 2001 From: Ari Becker Date: Wed, 18 Aug 2021 19:31:00 +0300 Subject: [PATCH 1/8] Initial release --- Dockerfile | 16 ++++++++ README.md | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/check | 3 ++ src/in | 3 ++ src/out | 27 +++++++++++++ 5 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100755 src/check create mode 100755 src/in create mode 100755 src/out diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b881a5a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM alpine:latest + +RUN apk --no-cache add bash jq curl + +LABEL org.label-schema.schema-version="1.0" \ + org.label-schema.name="pagerduty-event-resource" \ + org.label-schema.description="A Concourse resource for triggering PagerDuty incidents." \ + org.label-schema.vcs-url="https://github.com/coralogix/eng-concourse-resource-pagerduty-incident" \ + org.label-schema.vendor="Coralogix, Inc." \ + org.label-schema.version="v0.1.0" + +WORKDIR /opt/resource + +COPY src/check /opt/resource/check +COPY src/in /opt/resource/in +COPY src/out /opt/resource/out diff --git a/README.md b/README.md index 4bb6b6f..3a2f11b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,112 @@ # eng-concourse-resource-pagerduty-incident -A resource type for Concourse CI which creates a PagerDuty incident +A resource type for Concourse CI which creates a PagerDuty incident. + +Lightly adapted from the [PagerDuty documentation](https://developer.pagerduty.com/api-reference/reference/REST/openapiv3.json/paths/~1incidents/post). + +## Source Configuration +* `api_key` : _Required_ (`string`). The PagerDuty API key to use. +* `from_pagerduty_user` : _Required_ (`string`). The email address of a PagerDuty user + in whose name this Concourse resource is reporting the incident. +* `include_build_link` : _Optional_ (`bool`). If true, the resource will append a link + to the build in the body of the incident before sending it to + the PagerDuty API. Defaults to `true`. + +### Example Configuration + +Resource type definition + +```yaml +resource_types: + - name: pagerduty-incident + type: registry-image + source: + repository: ghcr.io/coralogix/eng-concourse-resource-pagerduty-incident + tag: v0.1.0 +``` + +Resource configuration + +```yaml +resources: + - name: pagerduty-incident + type: pagerduty-incident + source: + api_key: (( pagerduty.api_key )) + from_pagerduty_user: me@example.com +``` + +## Behavior + +### `check` : Not supported + +### `in` : Not supported + +### `out` : Create a PagerDuty Incident +Create an incident in PagerDuty. + +#### Params +* `incident` : _Required_ (`incident`). The incident to be created. To understand how to + populate this object, please refer to the PagerDuty documentation. + +### Example Usage + +Used in `on_abort`, `on_error`, and `on_failed` to alert a pipeline owner that the +pipeline has failed. + +```yaml +resource_types: + - name: pagerduty-incident + type: registry-image + source: + repository: ghcr.io/coralogix/eng-concourse-resource-pagerduty-incident + tag: v0.1.0 + +resources: + - name: pagerduty-incident + type: pagerduty-incident + source: + api_key: (( pagerduty.api_key )) + from_pagerduty_user: me@example.com + +jobs: + - name: my-critical-job + plan: + - task: critical-task + config: + platform: linux + image_resource: + type: registry-image + source: { repository: busybox } + run: + path: /bin/sh + args: + - '-c' + - 'echo "Oops, my critical task failed!" ; exit 1' + on_failure: + put: pagerduty-incident + params: + incident: + type: incident + title: "My pipeline's critical task failed!" + service: + # id is the PagerDuty service ID + id: P12345 + type: service_reference + urgency: high + body: + type: incident_body + details: "The pipeline's critical task failed, you should check why!" + escalation_policy: + # id is the PagerDuty escalation policy ID + id: P12345 + type: escalation_policy_reference +``` + +## Maintainers +* [Ari Becker](https://github.com/ari-becker) +* [Oded David](https://github.com/oded-dd) +* [Amit Oren](https://github.com/amit-o) +* [Shauli Solomovich](https://github.com/ShauliSolomovich) + +## License +[Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) © Coralogix, Inc. diff --git a/src/check b/src/check new file mode 100755 index 0000000..4c60806 --- /dev/null +++ b/src/check @@ -0,0 +1,3 @@ +#! /bin/sh + +echo '[{"hash":"none"}]' diff --git a/src/in b/src/in new file mode 100755 index 0000000..37d640e --- /dev/null +++ b/src/in @@ -0,0 +1,3 @@ +#! /bin/sh + +echo '{"version":{"hash":"none"},"metadata":[]}' diff --git a/src/out b/src/out new file mode 100755 index 0000000..697d041 --- /dev/null +++ b/src/out @@ -0,0 +1,27 @@ +#! /usr/bin/env bash + +set -euo pipefail + +input_json="$(cat)" + +api_key=$( echo "$input_json" | jq -r '.source.api_key' ) +from_pagerduty_user=$(echo "$input_json" | jq -r '.source.from_pagerduty_user') +include_build_link=$( echo "$input_json" | jq -r '.source.include_build_link? // true') +incident=$( echo "$input_json" | jq -c '.params' ) + +if [[ "$include_build_link" == 'true' ]]; then + incident=$(echo "$incident" | jq -c \ + --arg build_url "$ATC_EXTERNAL_URL/builds/$BUILD_ID" \ + '. * { "incident" : { "body" : { "details" : .incident.body.details + " Link to Build" } } }') +fi + +curl 1>&2 -XPOST \ + --header "Authorization: Token token=$api_key" \ + --header "Accept: application/vnd.pagerduty+json;version=2" \ + --header "Content-Type: application/json" \ + --header "From: $from_pagerduty_user" \ + --data "$incident" \ + --silent \ + https://api.pagerduty.com/incidents + +echo '{"version":{"hash":"none"},"metadata":[]}' From 0185b45598593c66530704cd354913184d41d149 Mon Sep 17 00:00:00 2001 From: Ari Becker Date: Thu, 19 Aug 2021 19:28:07 +0300 Subject: [PATCH 2/8] Added retries --- README.md | 3 +++ src/out | 24 ++++++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3a2f11b..19e2e0b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,9 @@ Lightly adapted from the [PagerDuty documentation](https://developer.pagerduty.c * `api_key` : _Required_ (`string`). The PagerDuty API key to use. * `from_pagerduty_user` : _Required_ (`string`). The email address of a PagerDuty user in whose name this Concourse resource is reporting the incident. +* `autogenerate_incident_key`: _Optional_ (`bool`). If true, the resource will set the + `incident_key` to the Concourse build URL. Defaults to + `true`. * `include_build_link` : _Optional_ (`bool`). If true, the resource will append a link to the build in the body of the incident before sending it to the PagerDuty API. Defaults to `true`. diff --git a/src/out b/src/out index 697d041..804a337 100755 --- a/src/out +++ b/src/out @@ -4,24 +4,36 @@ set -euo pipefail input_json="$(cat)" -api_key=$( echo "$input_json" | jq -r '.source.api_key' ) -from_pagerduty_user=$(echo "$input_json" | jq -r '.source.from_pagerduty_user') -include_build_link=$( echo "$input_json" | jq -r '.source.include_build_link? // true') -incident=$( echo "$input_json" | jq -c '.params' ) +api_key=$( echo "$input_json" | jq -r '.source.api_key' ) +from_pagerduty_user=$( echo "$input_json" | jq -r '.source.from_pagerduty_user') +autogenerate_incident_key=$(echo "$input_json" | jq -r '.source.autogenerate_incident_key? // true') +include_build_link=$( echo "$input_json" | jq -r '.source.include_build_link? // true') +incident=$( echo "$input_json" | jq -c '.params' ) + +build_url="$ATC_EXTERNAL_URL/builds/$BUILD_ID" + +if [[ "$autogenerate_incident_key" == 'true' ]]; then + incident=$(echo "$incident" | jq -c \ + --arg build_url "$build_url" \ + '. * { "incident" : { "incident_key" : $build_url } }') +fi if [[ "$include_build_link" == 'true' ]]; then incident=$(echo "$incident" | jq -c \ - --arg build_url "$ATC_EXTERNAL_URL/builds/$BUILD_ID" \ + --arg build_url "$build_url" \ '. * { "incident" : { "body" : { "details" : .incident.body.details + " Link to Build" } } }') fi +# note that --retry correctly retries only on timeouts and 429 (too many requests) not for unretriable errors +num_retries=10 # arbitrary hardcode curl 1>&2 -XPOST \ + --silent \ + --retry "$num_retries" \ --header "Authorization: Token token=$api_key" \ --header "Accept: application/vnd.pagerduty+json;version=2" \ --header "Content-Type: application/json" \ --header "From: $from_pagerduty_user" \ --data "$incident" \ - --silent \ https://api.pagerduty.com/incidents echo '{"version":{"hash":"none"},"metadata":[]}' From 9349910aaaa8988a7de8353fb36e03cf7f19cb20 Mon Sep 17 00:00:00 2001 From: Ari Becker Date: Thu, 19 Aug 2021 19:30:26 +0300 Subject: [PATCH 3/8] Remove silent from curl call --- src/out | 1 - 1 file changed, 1 deletion(-) diff --git a/src/out b/src/out index 804a337..b4663c5 100755 --- a/src/out +++ b/src/out @@ -27,7 +27,6 @@ fi # note that --retry correctly retries only on timeouts and 429 (too many requests) not for unretriable errors num_retries=10 # arbitrary hardcode curl 1>&2 -XPOST \ - --silent \ --retry "$num_retries" \ --header "Authorization: Token token=$api_key" \ --header "Accept: application/vnd.pagerduty+json;version=2" \ From fbf37fa8300954d4edaed769c83c4ce7172cbff0 Mon Sep 17 00:00:00 2001 From: Ari Becker Date: Thu, 19 Aug 2021 19:38:02 +0300 Subject: [PATCH 4/8] Add new curl options --- src/out | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/out b/src/out index b4663c5..596619a 100755 --- a/src/out +++ b/src/out @@ -28,6 +28,9 @@ fi num_retries=10 # arbitrary hardcode curl 1>&2 -XPOST \ --retry "$num_retries" \ + --no-progress-meter \ + --show-error \ + --fail-with-body \ --header "Authorization: Token token=$api_key" \ --header "Accept: application/vnd.pagerduty+json;version=2" \ --header "Content-Type: application/json" \ From fbb02e87206bf911565491457950af17c00031eb Mon Sep 17 00:00:00 2001 From: Ari Becker Date: Mon, 23 Aug 2021 16:03:49 +0300 Subject: [PATCH 5/8] Add Events v2 API --- README.md | 129 ++++++++++++++++++++++++++----- src/out | 223 +++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 306 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 19e2e0b..fbfa3aa 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,36 @@ # eng-concourse-resource-pagerduty-incident A resource type for Concourse CI which creates a PagerDuty incident. -Lightly adapted from the [PagerDuty documentation](https://developer.pagerduty.com/api-reference/reference/REST/openapiv3.json/paths/~1incidents/post). +Lightly adapted from the PagerDuty documentation of the [REST API](https://developer.pagerduty.com/api-reference/reference/REST/openapiv3.json/paths/~1incidents/post) and the [Events v2 API](https://developer.pagerduty.com/docs/events-api-v2/overview/) ## Source Configuration -* `api_key` : _Required_ (`string`). The PagerDuty API key to use. -* `from_pagerduty_user` : _Required_ (`string`). The email address of a PagerDuty user - in whose name this Concourse resource is reporting the incident. -* `autogenerate_incident_key`: _Optional_ (`bool`). If true, the resource will set the - `incident_key` to the Concourse build URL. Defaults to - `true`. -* `include_build_link` : _Optional_ (`bool`). If true, the resource will append a link - to the build in the body of the incident before sending it to - the PagerDuty API. Defaults to `true`. +You are required to configure the resource either as targeting the REST API or as targeting the Events v2 API. + +Do note that the REST API is *synchronous* while the Events v2 API is *asynchronous*. This means that, if you use the +Events v2 API, Concourse will not wait until the event has triggered an incident in PagerDuty, but that if you use the +REST API, the API waits until after the incident has been created and as such Concourse will wait until after the incident is +created to proceed. + +### REST API +* `rest1.api_key` : _Required_ (`string`). The PagerDuty API key to use. +* `rest.from_pagerduty_user` : _Required_ (`string`). The email address of a PagerDuty user + in whose name this Concourse resource is reporting the incident. +* `rest.autogenerate_incident_key`: _Optional_ (`bool`). If true, the resource will set the + `incident_key` to the Concourse build URL. Defaults to + `true`. +* `rest.include_build_link` : _Optional_ (`bool`). If true, the resource will append a link + to the build in the body of the incident before sending it to + the PagerDuty API. Defaults to `true`. + +### Events v2 API +* `events_v2.routing_key`: _Required_ (`string`). The routing key for the PagerDuty Events V2 API integration. +* `events_v2.attach_build_url_to_links`: _Optional_ (`bool`). If true, the resource will attach a link to the + build in the payload's links section. Defaults to `true`. +* `events_v2.attach_timestamp`: _Optional_ (`bool`). If true, and if a timestamp is not provided in the + put parameters, the resource will call `date` to get a timestamp + and attach the timestamp to the payload. If this is false and no timestamp + is provided, then PagerDuty will apply a default timestamp when PagerDuty receives + the payload. Defaults to `true`. ### Example Configuration @@ -27,15 +45,28 @@ resource_types: tag: v0.1.0 ``` -Resource configuration +Resource configuration (REST API) ```yaml resources: - name: pagerduty-incident type: pagerduty-incident source: - api_key: (( pagerduty.api_key )) - from_pagerduty_user: me@example.com + rest: + api_key: (( pagerduty.api_key )) + from_pagerduty_user: me@example.com +``` + +Resource configuration (Events v2 API) + +```yaml +resources: + - name: pagerduty-incident + type: pagerduty-incident + source: + events_v2: + routing_key: ((pagerduty.routing_key)) + attach_timestamp: true ``` ## Behavior @@ -45,17 +76,36 @@ resources: ### `in` : Not supported ### `out` : Create a PagerDuty Incident -Create an incident in PagerDuty. +Create an incident or push an event to PagerDuty. -#### Params +#### Params (REST API) * `incident` : _Required_ (`incident`). The incident to be created. To understand how to populate this object, please refer to the PagerDuty documentation. -### Example Usage +#### Params (Events v2 API) +* `event_type`: _Optional_ (`enum`). Must be either `alert` or `change`. Defaults to `alert`. +* `event_action`: _Optional_ (`enum`). For alerts, must be either `trigger`, `acknowledge`, or `resolve`. + Defaults to `trigger`. +* `dedup_key`: _Optional_ (`string`). The deduplication key. Defaults to the build URL (i.e. `$ATC_EXTERNAL_URL/builds/$BUILD_ID`). +* `summary`: _Optional_ (`string`). The event's summary. Required, unless this is an `alert` event of action `acknowledge` or `resolve`. +* `source`: _Optional_ (`string`). The event's source. Required if this is an `alert` event of action `trigger`. +* `severity`: _Optional_ (`string`). The event's severity. Required if this is an `alert` event of action `trigger`. +* `source`: _Optional_ (`string`). The event's source. +* `timestamp`: _Optional_ (`ISO-8601 timestamp`). The event's timestamp. You would presumably use this field in a pipeline configuration + by populating the field with a timestamp created in an earlier step with the help of Concourse's `load_var`. +* `component`: _Optional_ (`string`). The event's component. +* `group`: _Optional_ (`string`). The event's group. +* `class`: _Optional_ (`string`). The event's class. +* `custom_details`: _Optional_ (`object`). The event's custom details. +* `custom_details_file`: _Optional_ (`file path`). A path to a JSON file with additional custom details, i.e. generated in a previous step. +* `images`: _Optional_ (`list(object({src: string, href: optional string, alt: optional string}))`). The event's images. +* `links`: _Optional_ (`list(object({href: string, text: optional string}))`). The event's links. +### Example Usage Used in `on_abort`, `on_error`, and `on_failed` to alert a pipeline owner that the pipeline has failed. +#### REST API ```yaml resource_types: - name: pagerduty-incident @@ -68,8 +118,9 @@ resources: - name: pagerduty-incident type: pagerduty-incident source: - api_key: (( pagerduty.api_key )) - from_pagerduty_user: me@example.com + rest: + api_key: (( pagerduty.api_key )) + from_pagerduty_user: me@example.com jobs: - name: my-critical-job @@ -105,6 +156,48 @@ jobs: type: escalation_policy_reference ``` +#### Events v2 API +```yaml +resource_types: + - name: pagerduty-incident + type: registry-image + source: + repository: ghcr.io/coralogix/eng-concourse-resource-pagerduty-incident + tag: v0.1.0 + +resources: + - name: pagerduty-incident + type: pagerduty-incident + source: + events_v2: + routing_key: (( pagerduty.api_key )) + +jobs: + - name: my-critical-job + plan: + - put: pagerduty-incident + params: + event_type: change + summary: "About to try a critical task" + - task: critical-task + config: + platform: linux + image_resource: + type: registry-image + source: { repository: busybox } + run: + path: /bin/sh + args: + - '-c' + - 'echo "Oops, my critical task failed!" ; exit 1' + on_failure: + put: pagerduty-incident + params: + summary: "My pipeline's critical task failed!" + source: "Concourse Pipeline" + severity: critical +``` + ## Maintainers * [Ari Becker](https://github.com/ari-becker) * [Oded David](https://github.com/oded-dd) diff --git a/src/out b/src/out index 596619a..215aed6 100755 --- a/src/out +++ b/src/out @@ -4,38 +4,205 @@ set -euo pipefail input_json="$(cat)" -api_key=$( echo "$input_json" | jq -r '.source.api_key' ) -from_pagerduty_user=$( echo "$input_json" | jq -r '.source.from_pagerduty_user') -autogenerate_incident_key=$(echo "$input_json" | jq -r '.source.autogenerate_incident_key? // true') -include_build_link=$( echo "$input_json" | jq -r '.source.include_build_link? // true') -incident=$( echo "$input_json" | jq -c '.params' ) +rest_api=$( echo "$input_json" | jq -cM '.source.rest') +events_v2_api=$( echo "$input_json" | jq -cM '.source.events_v2') -build_url="$ATC_EXTERNAL_URL/builds/$BUILD_ID" - -if [[ "$autogenerate_incident_key" == 'true' ]]; then - incident=$(echo "$incident" | jq -c \ - --arg build_url "$build_url" \ - '. * { "incident" : { "incident_key" : $build_url } }') +if [[ "$rest_api" == 'null' ]] && [[ "$events_v2_api" == 'null' ]]; then + echo >&2 '[ERROR] You must define either "rest" or "events_v2" in the resource source - you cannot leave them both undefined!' + exit 1 fi -if [[ "$include_build_link" == 'true' ]]; then - incident=$(echo "$incident" | jq -c \ - --arg build_url "$build_url" \ - '. * { "incident" : { "body" : { "details" : .incident.body.details + " Link to Build" } } }') +if [[ "$rest_api" != 'null' ]] && [[ "$events_v2_api" != 'null' ]]; then + echo >&2 '[ERROR] You must define either "rest" or "events_v2" in the resource source - you cannot define both of them at once!' + exit 1 fi -# note that --retry correctly retries only on timeouts and 429 (too many requests) not for unretriable errors -num_retries=10 # arbitrary hardcode -curl 1>&2 -XPOST \ - --retry "$num_retries" \ - --no-progress-meter \ - --show-error \ - --fail-with-body \ - --header "Authorization: Token token=$api_key" \ - --header "Accept: application/vnd.pagerduty+json;version=2" \ - --header "Content-Type: application/json" \ - --header "From: $from_pagerduty_user" \ - --data "$incident" \ - https://api.pagerduty.com/incidents +build_url="$ATC_EXTERNAL_URL/builds/$BUILD_ID" + +if [[ "$rest_api" != 'null' ]]; then + api_key=$( echo "$input_json" | jq -r '.source.rest.api_key' ) + from_pagerduty_user=$( echo "$input_json" | jq -r '.source.rest.from_pagerduty_user') + autogenerate_incident_key=$(echo "$input_json" | jq -r '.source.rest.autogenerate_incident_key? // true') + include_build_link=$( echo "$input_json" | jq -r '.source.rest.include_build_link? // true') + incident=$( echo "$input_json" | jq -c '.params' ) + + + if [[ "$autogenerate_incident_key" == 'true' ]]; then + incident=$(echo "$incident" | jq -c \ + --arg build_url "$build_url" \ + '. * { "incident" : { "incident_key" : $build_url } }') + fi + + if [[ "$include_build_link" == 'true' ]]; then + incident=$(echo "$incident" | jq -c \ + --arg build_url "$build_url" \ + '. * { "incident" : { "body" : { "details" : .incident.body.details + " Link to Build" } } }') + fi + + # note that --retry correctly retries only on timeouts and 429 (too many requests) not for unretriable errors + num_retries=10 # arbitrary hardcode + curl 1>&2 -XPOST \ + --retry "$num_retries" \ + --no-progress-meter \ + --show-error \ + --fail-with-body \ + --header "Authorization: Token token=$api_key" \ + --header "Accept: application/vnd.pagerduty+json;version=2" \ + --header "Content-Type: application/json" \ + --header "From: $from_pagerduty_user" \ + --data "$incident" \ + https://api.pagerduty.com/incidents + +elif [[ "$events_v2_api" != 'null' ]]; then + routing_key=$( echo "$input_json" | jq -r '.source.events_v2.routing_key? // ""') + attach_build_url_to_links=$(echo "$input_json" | jq -r '.source.events_v2.attach_build_url_to_links? // true') + attach_timestamp=$( echo "$input_json" | jq -r '.source.events_v2.attach_timestamp? // true') + + event_type=$( echo "$input_json" | jq -r '.params.event_type? // "alert"') + event_action=$( echo "$input_json" | jq -r '.params.event_action? // "trigger"') + dedup_key=$( echo "$input_json" | jq -r --arg dedup_key "$build_url" '.params.dedup_key? // $dedup_key') + summary=$( echo "$input_json" | jq -r '.params.summary? // ""') + source_=$( echo "$input_json" | jq -r '.params.source? // ""') + severity=$( echo "$input_json" | jq -r '.params.severity? // ""') + timestamp=$( echo "$input_json" | jq -r '.params.timestamp? // ""') + component=$( echo "$input_json" | jq -r '.params.component? // ""') + group=$( echo "$input_json" | jq -r '.params.group? // ""') + class=$( echo "$input_json" | jq -r '.params.class? // ""') + custom_details=$( echo "$input_json" | jq -r '.params.custom_details? // {}') + custom_details_file=$(echo "$input_json" | jq -r '.params.custom_details_file? // ""') + images=$( echo "$input_json" | jq -r '.params.images? // []') + links=$( echo "$input_json" | jq -r '.params.links? // []') + + if [[ -z "$routing_key" ]]; then + echo >&2 "[ERROR] You must define a routing_key!" + exit 1 + fi + + if [[ "$event_type" != 'alert' ]] && [[ "$event_type" != 'change' ]]; then + echo >&2 "[ERROR][event_type: $event_type] Unrecognized event type, must be either 'alert' or 'change'" + exit 1 + fi + + dedup_key_length=$(echo -n "$dedup_key" | wc -c) + if [[ $dedup_key_length -gt 255 ]]; then + echo >&2 "[ERROR][dedup_key: $dedup_key][length: $dedup_key_length] The dedup_key is longer than the PagerDuty API maximum length of 255 characters!" + exit 1 + fi + + summary_length=$(echo -n "$summary" | wc -c) + if [[ $summery_length -gt 1024 ]]; then + echo >&2 "[ERROR][summary: $summary][length: $summary_length] The summary is longer than the PagerDuty API maximum length of 1024 characters!" + exit 1 + fi + + if [[ "$event_type" == 'alert' ]]; then + if [[ "$event_action" == 'trigger' ]] && [[ -z "$summary" ]] ; then + echo >&2 "[ERROR][summary: $summary] You must define a summary, it is required by PagerDuty!" + exit 1 + fi + + if [[ "$event_action" == 'trigger' ]] && [[ -z "$source" ]]; then + echo >&2 "[ERROR][source: $source] You must define a source, it is required by PagerDuty!" + exit 1 + fi + + if [[ "$event_action" == 'trigger' ]] && [[ -z "$severity" ]]; then + echo >&2 "[ERROR][severity: $severity] You must define a severity, it is required by PagerDuty! You may choose from: critical, error, warning, info" + exit 1 + fi + + if [[ "$event_action" == 'trigger' ]] && [[ -z "$timestamp" ]] && [[ "$attach_timestamp" == 'true' ]]; then + timestamp=$(date --iso-8601=ns) + fi + + if [[ "$event_action" == 'trigger' ]] && [[ -n "$custom_details_file" ]]; then + custom_details=$(jq -nc \ + --argjson custom_details "$custom_details" \ + --slurpfile additional_custom_details "$custom_details_file" \ + '$custom_details * $additional_custom_details') + fi + + if [[ "$event_action" == 'trigger' ]] && [[ "$attach_build_url_to_links" == 'true' ]]; then + links=$(jq -nc \ + --argjson links "$links" \ + --arg build_url "$build_url" \ + '$links + [{"text": "Link to Build", "href": $build_url}]') + fi + elif [[ "$event_type" == 'change' ]]; then + if [[ -z "$summary" ]] ; then + echo >&2 "[ERROR][summary: $summary] You must define a summary, it is required by PagerDuty!" + exit 1 + fi + if [[ -z "$timestamp" ]] && [[ "$attach_timestamp" == 'true' ]]; then + timestamp=$(date --iso-8601=ns) + fi + if [[ -n "$custom_details_file" ]]; then + custom_details=$(jq -nc \ + --argjson custom_details "$custom_details" \ + --slurpfile additional_custom_details "$custom_details_file" \ + '$custom_details * $additional_custom_details') + fi + if [[ "$attach_build_url_to_links" == 'true' ]]; then + links=$(jq -nc \ + --argjson links "$links" \ + --arg build_url "$build_url" \ + '$links + [{"text": "Link to Build", "href": $build_url}]') + fi + fi + + # required payload + payload=$(jq -cn + --arg routing_key "$routing_key" + --arg event_action "$event_action" + '{"routing_key": $routing_key, "event_action": $event_action }') + + if [[ -n "$dedup_key" ]]; then + payload=$(echo "$payload" | jq -cn --arg dedup_key "$dedup_key" '.* {"dedup_key: $dedup_key}') + fi + if [[ -n "$summary" ]]; then + payload=$(echo "$payload" | jq -cn --arg summary "$summary" '. * {"payload":{"summary": $summary}}') + fi + if [[ -n "$source_" ]]; then + payload=$(echo "$payload" | jq -cn --arg source_ "$source_" '. * {"payload":{"source": $source_}}') + fi + if [[ -n "$severity" ]]; then + payload=$(echo "$payload" | jq -cn --arg severity "$severity" '. * {"payload":{"severity": $severity}}') + fi + if [[ -n "$timestamp" ]]; then + payload=$(echo "$payload" | jq -cn --arg timestamp "$timestamp" '. * {"payload":{"timestamp": $timestamp}}') + fi + if [[ -n "$component" ]]; then + payload=$(echo "$payload" | jq -cn --arg component "$component" '. * {"payload":{"component": $component}}') + fi + if [[ -n "$group" ]]; then + payload=$(echo "$payload" | jq -cn --arg group "$group" '. * {"payload":{"group": $group}}') + fi + if [[ -n "$class" ]]; then + payload=$(echo "$payload" | jq -cn --arg class "$class" '. * {"payload":{"class": $class}}') + fi + if [[ "$custom_details" != '{}' ]]; then + payload=$(echo "$payload" | jq -cn --argjson custom_details "$custom_details" '. * {"payload":{"custom_details": $custom_details}}') + fi + if [[ "$images" != '[]' ]]; then + payload=$(echo "$payload" | jq -cn --argjson images "$images" '. * {"payload":{"images": $images}}') + fi + if [[ "$links" != '[]' ]]; then + payload=$(echo "$payload" | jq -cn --argjson links "$links" '. * {"payload":{"links": $links}}') + fi + + # note that --retry correctly retries only on timeouts and 429 (too many requests) not for unretriable errors + pagerduty_endpoint='https://events.pagerduty.com/v2/enqueue' + if [[ "$event_type" == 'change' ]]; then + pagerduty_endpoint='https://events.pagerduty.com/v2/change/enqueue' + fi + num_retries=10 # arbitrary hardcode + curl 1>&2 -XPOST \ + --retry "$num_retries" \ + --no-progress-meter \ + --show-error \ + --fail-with-body \ + --data "$payload" \ + "$pagerduty_endpoint" +fi echo '{"version":{"hash":"none"},"metadata":[]}' From 1479615d291d43065681a2a2cf5400dbd7f733ad Mon Sep 17 00:00:00 2001 From: Ari Becker Date: Mon, 23 Aug 2021 16:19:10 +0300 Subject: [PATCH 6/8] Add client to Events v2 --- README.md | 2 ++ src/out | 35 ++++++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fbfa3aa..5e82d04 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ created to proceed. ### Events v2 API * `events_v2.routing_key`: _Required_ (`string`). The routing key for the PagerDuty Events V2 API integration. +* `events_v2.client`: _Optional_ (`string`). When triggering alerts, the client to attach to the payload. Defaults to `Concourse`. +* `events_v2.client_url`: _Optional_ (`string`). When triggering alerts, the client URL to attach to the payload. Defaults to `$ATC_EXTERNAL_URL`. * `events_v2.attach_build_url_to_links`: _Optional_ (`bool`). If true, the resource will attach a link to the build in the payload's links section. Defaults to `true`. * `events_v2.attach_timestamp`: _Optional_ (`bool`). If true, and if a timestamp is not provided in the diff --git a/src/out b/src/out index 215aed6..64cbaaf 100755 --- a/src/out +++ b/src/out @@ -55,6 +55,8 @@ if [[ "$rest_api" != 'null' ]]; then elif [[ "$events_v2_api" != 'null' ]]; then routing_key=$( echo "$input_json" | jq -r '.source.events_v2.routing_key? // ""') + client=$( echo "$input_json" | jq -r '.source.events_v2.client? // ""') + client_url=$( echo "$input_json" | jq -r '.source.events_v2.client_url? // ""') attach_build_url_to_links=$(echo "$input_json" | jq -r '.source.events_v2.attach_build_url_to_links? // true') attach_timestamp=$( echo "$input_json" | jq -r '.source.events_v2.attach_timestamp? // true') @@ -95,44 +97,57 @@ elif [[ "$events_v2_api" != 'null' ]]; then exit 1 fi - if [[ "$event_type" == 'alert' ]]; then - if [[ "$event_action" == 'trigger' ]] && [[ -z "$summary" ]] ; then + if [[ "$event_type" == 'alert' ]] && [[ "$event_action" == 'trigger' ]] ; then + if [[ -z "$summary" ]] ; then echo >&2 "[ERROR][summary: $summary] You must define a summary, it is required by PagerDuty!" exit 1 fi - if [[ "$event_action" == 'trigger' ]] && [[ -z "$source" ]]; then + if [[ -z "$source" ]]; then echo >&2 "[ERROR][source: $source] You must define a source, it is required by PagerDuty!" exit 1 fi - if [[ "$event_action" == 'trigger' ]] && [[ -z "$severity" ]]; then + if [[ -z "$severity" ]]; then echo >&2 "[ERROR][severity: $severity] You must define a severity, it is required by PagerDuty! You may choose from: critical, error, warning, info" exit 1 fi - if [[ "$event_action" == 'trigger' ]] && [[ -z "$timestamp" ]] && [[ "$attach_timestamp" == 'true' ]]; then + if [[ -n "$client" ]]; then + client='Concourse' + fi + + if [[ -n "$client_url" ]]; then + client_url="$ATC_EXTERNAL_URL" + fi + + if [[ -z "$timestamp" ]] && [[ "$attach_timestamp" == 'true' ]]; then timestamp=$(date --iso-8601=ns) fi - if [[ "$event_action" == 'trigger' ]] && [[ -n "$custom_details_file" ]]; then + if [[ -n "$custom_details_file" ]]; then custom_details=$(jq -nc \ --argjson custom_details "$custom_details" \ --slurpfile additional_custom_details "$custom_details_file" \ '$custom_details * $additional_custom_details') fi - if [[ "$event_action" == 'trigger' ]] && [[ "$attach_build_url_to_links" == 'true' ]]; then + if [[ "$attach_build_url_to_links" == 'true' ]]; then links=$(jq -nc \ --argjson links "$links" \ --arg build_url "$build_url" \ '$links + [{"text": "Link to Build", "href": $build_url}]') fi + elif [[ "$event_type" == 'alert' ]]; then + client='' + client_url='' elif [[ "$event_type" == 'change' ]]; then if [[ -z "$summary" ]] ; then echo >&2 "[ERROR][summary: $summary] You must define a summary, it is required by PagerDuty!" exit 1 fi + client='' + client_url='' if [[ -z "$timestamp" ]] && [[ "$attach_timestamp" == 'true' ]]; then timestamp=$(date --iso-8601=ns) fi @@ -159,6 +174,12 @@ elif [[ "$events_v2_api" != 'null' ]]; then if [[ -n "$dedup_key" ]]; then payload=$(echo "$payload" | jq -cn --arg dedup_key "$dedup_key" '.* {"dedup_key: $dedup_key}') fi + if [[ -n "$client" ]]; then + payload=$(echo "$payload" | jq -cn --arg client "$client" ' .* {"client": $client}') + fi + if [[ -n "$client_url" ]]; then + payload=$(echo "$payload" | jq -cn --arg client_url "$client_url" ' .* {"client_url": $client_url}') + fi if [[ -n "$summary" ]]; then payload=$(echo "$payload" | jq -cn --arg summary "$summary" '. * {"payload":{"summary": $summary}}') fi From 06b03659ae966c150fd963dd6603d2de7325a90c Mon Sep 17 00:00:00 2001 From: Ari Becker Date: Tue, 24 Aug 2021 11:59:16 +0300 Subject: [PATCH 7/8] Fixes --- Dockerfile | 2 +- README.md | 4 ++++ src/out | 40 +++++++++++++++++++++------------------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index b881a5a..62a810a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:latest -RUN apk --no-cache add bash jq curl +RUN apk --no-cache add bash coreutils curl jq LABEL org.label-schema.schema-version="1.0" \ org.label-schema.name="pagerduty-event-resource" \ diff --git a/README.md b/README.md index 5e82d04..ce8f91e 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Resource configuration (REST API) resources: - name: pagerduty-incident type: pagerduty-incident + check_every: never source: rest: api_key: (( pagerduty.api_key )) @@ -65,6 +66,7 @@ Resource configuration (Events v2 API) resources: - name: pagerduty-incident type: pagerduty-incident + check_every: never source: events_v2: routing_key: ((pagerduty.routing_key)) @@ -119,6 +121,7 @@ resource_types: resources: - name: pagerduty-incident type: pagerduty-incident + check_every: never source: rest: api_key: (( pagerduty.api_key )) @@ -170,6 +173,7 @@ resource_types: resources: - name: pagerduty-incident type: pagerduty-incident + check_every: never source: events_v2: routing_key: (( pagerduty.api_key )) diff --git a/src/out b/src/out index 64cbaaf..ca0eada 100755 --- a/src/out +++ b/src/out @@ -92,7 +92,7 @@ elif [[ "$events_v2_api" != 'null' ]]; then fi summary_length=$(echo -n "$summary" | wc -c) - if [[ $summery_length -gt 1024 ]]; then + if [[ $summary_length -gt 1024 ]]; then echo >&2 "[ERROR][summary: $summary][length: $summary_length] The summary is longer than the PagerDuty API maximum length of 1024 characters!" exit 1 fi @@ -103,8 +103,8 @@ elif [[ "$events_v2_api" != 'null' ]]; then exit 1 fi - if [[ -z "$source" ]]; then - echo >&2 "[ERROR][source: $source] You must define a source, it is required by PagerDuty!" + if [[ -z "$source_" ]]; then + echo >&2 "[ERROR][source: $source_] You must define a source, it is required by PagerDuty!" exit 1 fi @@ -166,49 +166,49 @@ elif [[ "$events_v2_api" != 'null' ]]; then fi # required payload - payload=$(jq -cn - --arg routing_key "$routing_key" - --arg event_action "$event_action" + payload=$(jq -cn \ + --arg routing_key "$routing_key" \ + --arg event_action "$event_action" \ '{"routing_key": $routing_key, "event_action": $event_action }') if [[ -n "$dedup_key" ]]; then - payload=$(echo "$payload" | jq -cn --arg dedup_key "$dedup_key" '.* {"dedup_key: $dedup_key}') + payload=$(echo "$payload" | jq -c --arg dedup_key "$dedup_key" '.* {"dedup_key": $dedup_key}') fi if [[ -n "$client" ]]; then - payload=$(echo "$payload" | jq -cn --arg client "$client" ' .* {"client": $client}') + payload=$(echo "$payload" | jq -c --arg client "$client" ' .* {"client": $client}') fi if [[ -n "$client_url" ]]; then - payload=$(echo "$payload" | jq -cn --arg client_url "$client_url" ' .* {"client_url": $client_url}') + payload=$(echo "$payload" | jq -c --arg client_url "$client_url" ' .* {"client_url": $client_url}') fi if [[ -n "$summary" ]]; then - payload=$(echo "$payload" | jq -cn --arg summary "$summary" '. * {"payload":{"summary": $summary}}') + payload=$(echo "$payload" | jq -c --arg summary "$summary" '. * {"payload":{"summary": $summary}}') fi if [[ -n "$source_" ]]; then - payload=$(echo "$payload" | jq -cn --arg source_ "$source_" '. * {"payload":{"source": $source_}}') + payload=$(echo "$payload" | jq -c --arg source_ "$source_" '. * {"payload":{"source": $source_}}') fi if [[ -n "$severity" ]]; then - payload=$(echo "$payload" | jq -cn --arg severity "$severity" '. * {"payload":{"severity": $severity}}') + payload=$(echo "$payload" | jq -c --arg severity "$severity" '. * {"payload":{"severity": $severity}}') fi if [[ -n "$timestamp" ]]; then - payload=$(echo "$payload" | jq -cn --arg timestamp "$timestamp" '. * {"payload":{"timestamp": $timestamp}}') + payload=$(echo "$payload" | jq -c --arg timestamp "$timestamp" '. * {"payload":{"timestamp": $timestamp}}') fi if [[ -n "$component" ]]; then - payload=$(echo "$payload" | jq -cn --arg component "$component" '. * {"payload":{"component": $component}}') + payload=$(echo "$payload" | jq -c --arg component "$component" '. * {"payload":{"component": $component}}') fi if [[ -n "$group" ]]; then - payload=$(echo "$payload" | jq -cn --arg group "$group" '. * {"payload":{"group": $group}}') + payload=$(echo "$payload" | jq -c --arg group "$group" '. * {"payload":{"group": $group}}') fi if [[ -n "$class" ]]; then - payload=$(echo "$payload" | jq -cn --arg class "$class" '. * {"payload":{"class": $class}}') + payload=$(echo "$payload" | jq -c --arg class "$class" '. * {"payload":{"class": $class}}') fi if [[ "$custom_details" != '{}' ]]; then - payload=$(echo "$payload" | jq -cn --argjson custom_details "$custom_details" '. * {"payload":{"custom_details": $custom_details}}') + payload=$(echo "$payload" | jq -c --argjson custom_details "$custom_details" '. * {"payload":{"custom_details": $custom_details}}') fi if [[ "$images" != '[]' ]]; then - payload=$(echo "$payload" | jq -cn --argjson images "$images" '. * {"payload":{"images": $images}}') + payload=$(echo "$payload" | jq -c --argjson images "$images" '. * {"images": $images}') fi if [[ "$links" != '[]' ]]; then - payload=$(echo "$payload" | jq -cn --argjson links "$links" '. * {"payload":{"links": $links}}') + payload=$(echo "$payload" | jq -c --argjson links "$links" '. * {"links": $links}') fi # note that --retry correctly retries only on timeouts and 429 (too many requests) not for unretriable errors @@ -216,12 +216,14 @@ elif [[ "$events_v2_api" != 'null' ]]; then if [[ "$event_type" == 'change' ]]; then pagerduty_endpoint='https://events.pagerduty.com/v2/change/enqueue' fi + num_retries=10 # arbitrary hardcode curl 1>&2 -XPOST \ --retry "$num_retries" \ --no-progress-meter \ --show-error \ --fail-with-body \ + --header "Content-Type: application/json" \ --data "$payload" \ "$pagerduty_endpoint" fi From cd5686758f2223ad44edf89d96f60af798664e0c Mon Sep 17 00:00:00 2001 From: Ari Becker Date: Tue, 24 Aug 2021 12:03:57 +0300 Subject: [PATCH 8/8] Clarify readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ce8f91e..809fc2b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # eng-concourse-resource-pagerduty-incident -A resource type for Concourse CI which creates a PagerDuty incident. +A resource type for Concourse CI which creates a PagerDuty incident, either through the REST API or through the Events v2 API. It also supports submitting change events through the Events v2 API. Lightly adapted from the PagerDuty documentation of the [REST API](https://developer.pagerduty.com/api-reference/reference/REST/openapiv3.json/paths/~1incidents/post) and the [Events v2 API](https://developer.pagerduty.com/docs/events-api-v2/overview/)