Skip to content

Commit c61f87c

Browse files
committed
Change generic webhook API
1 parent ad0745b commit c61f87c

File tree

12 files changed

+248
-92
lines changed

12 files changed

+248
-92
lines changed

.github/workflows/ci.yml

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,22 @@ jobs:
5858
- name: Checkout
5959
uses: actions/checkout@v4
6060

61+
- name: Install Git
62+
run: |
63+
sudo apt-get update
64+
sudo apt-get install git -y
65+
- name: Set up SSH agent (for service-registry)
66+
uses: webfactory/[email protected]
67+
with:
68+
ssh-private-key: ${{ secrets.SENTRY_INTERNAL_GH_SSH_PRIVATE_KEY }}
69+
6170
- name: Setup node
6271
uses: actions/setup-node@v4
6372
with:
6473
node-version: '18'
6574

6675
- name: yarn install
67-
run: yarn install --immutable
76+
run: yarn install --immutable && yarn up "service-registry@git+ssh://[email protected]:getsentry/service-registry#main"
6877

6978
- name: tsc
7079
run: yarn build
@@ -85,8 +94,13 @@ jobs:
8594
steps:
8695
- uses: actions/checkout@v3
8796

97+
- name: Set up SSH agent (for service-registry)
98+
uses: webfactory/[email protected]
99+
with:
100+
ssh-private-key: ${{ secrets.SENTRY_INTERNAL_GH_SSH_PRIVATE_KEY }}
101+
88102
- name: Builds docker image
89-
run: docker build -t ci-tooling .
103+
run: DOCKER_BUILDKIT=1 docker build --ssh default -t ci-tooling .
90104

91105
build-deploy:
92106
name: build and deploy
@@ -110,14 +124,22 @@ jobs:
110124
with:
111125
# for Sentry releases
112126
fetch-depth: 0
127+
- name: Install Git
128+
run: |
129+
sudo apt-get update
130+
sudo apt-get install git -y
131+
- name: Set up SSH agent (for service-registry)
132+
uses: webfactory/[email protected]
133+
with:
134+
ssh-private-key: ${{ secrets.SENTRY_INTERNAL_GH_SSH_PRIVATE_KEY }}
113135

114136
- name: Setup node
115137
uses: actions/setup-node@v4
116138
with:
117139
node-version: '18'
118140

119141
- name: yarn install
120-
run: yarn install --immutable
142+
run: yarn install --immutable && yarn up "service-registry@git+ssh://[email protected]:getsentry/service-registry#main"
121143

122144
- name: tsc
123145
run: yarn build

.github/workflows/migration.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ jobs:
3333
steps:
3434
- name: Checkout
3535
uses: actions/checkout@v4
36+
- name: Install Git
37+
run: |
38+
sudo apt-get update
39+
sudo apt-get install git -y
40+
- name: Set up SSH agent (for service-registry)
41+
uses: webfactory/[email protected]
42+
with:
43+
ssh-private-key: ${{ secrets.SENTRY_INTERNAL_GH_SSH_PRIVATE_KEY }}
3644

3745
- name: Setup node
3846
uses: actions/setup-node@v4
@@ -59,7 +67,7 @@ jobs:
5967
6068
- name: yarn install
6169
run: |
62-
yarn install --immutable
70+
yarn install --immutable && yarn up "service-registry@git+ssh://[email protected]:getsentry/service-registry#main"
6371
6472
- name: Run migration
6573
env:

bin/deploy.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ GOCD_WEBHOOK_SECRET
2727
KAFKA_CONTROL_PLANE_WEBHOOK_SECRET
2828
SENTRY_OPTIONS_WEBHOOK_SECRET
2929
"
30+
# TODO: Revamp this and make it easier to add secrets & deploy to GCP
3031

3132
secrets=""
3233
for secret_name in $secret_names; do

src/config/secrets.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ This file contains secrets used for verifying incoming events from different HTT
66

77
export const EVENT_NOTIFIER_SECRETS = {
88
// Follow the pattern below to add a new secret
9+
// The secret will also need to be added in the deploy.sh script and in
10+
// Google Secret manager
911
// 'example-service': process.env.EXAMPLE_SERVICE_SECRET,
1012
};
1113
if (process.env.ENV !== 'production')

src/types/index.ts

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,38 @@ export interface KafkaControlPlaneResponse {
2929
body: string;
3030
}
3131

32+
export interface SlackMessage {
33+
type: 'slack';
34+
channels: string[];
35+
text: string;
36+
blocks?: KnownBlock[] | Block[];
37+
}
38+
39+
export interface DatadogEvent {
40+
type: 'datadog';
41+
title: string;
42+
text: string;
43+
tags: string[];
44+
alertType: EventAlertType;
45+
}
46+
47+
export interface JiraEvent {
48+
type: 'jira';
49+
projectId: string;
50+
title: string;
51+
}
52+
3253
export type GenericEvent = {
3354
source: string;
3455
timestamp: number;
35-
service_name?: string; // Official service registry name if applicable
36-
data: {
37-
title: string;
38-
message: string;
39-
channels: {
40-
slack?: string[]; // list of Slack Channels
41-
datadog?: string[]; // list of DD Monitors
42-
jira?: string[]; // list of Jira Projects
43-
bigquery?: string;
44-
};
45-
tags?: string[]; // Not used for Slack
46-
misc: {
47-
alertType?: EventAlertType; // Datadog alert type
48-
blocks?: (KnownBlock | Block)[]; // Optional Slack blocks
49-
};
50-
};
56+
data: (DatadogEvent | JiraEvent | SlackMessage | ServiceSlackMessage)[];
5157
};
58+
59+
// Currently only used for Slack notifications since
60+
// service registry only contains Slack channels (and not DD or Jira or others)
61+
export interface ServiceSlackMessage {
62+
type: 'service_notification';
63+
service_name: string; // Official service registry service id
64+
text: string;
65+
blocks?: KnownBlock[] | Block[];
66+
}

src/utils/misc/serviceRegistry.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import servicesData from 'service-registry/sentry_service_registry/config/combined/service_registry.json';
2+
import type { Service, ServiceRegistry } from 'service-registry/types/index';
3+
4+
const services: ServiceRegistry = servicesData;
5+
6+
export function getService(serviceId: string): Service {
7+
return services[serviceId];
8+
}

src/webhooks/README.md

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,35 @@
77

88
The folder `generic-notifier` provides a generic webhook which can be used to send messages to Sentry Slack channels and Sentry Datadog. Using this webhook is VERY simple.
99

10-
Simply, go to `@/config/secrets.ts` and add an entry to the `EVENT_NOTIFIER_SECRETS` object. This entry should contain a mapping from the name of your service (for example, `example-service`) to an environment variable. [TODO: Fill in how to set the prod env var here]. Make a PR with this change and get it approved & merged.
10+
Simply, go to `@/config/secrets.ts` and add an entry to the `EVENT_NOTIFIER_SECRETS` object. This entry should contain a mapping from the source of the message (for example, `example-service`) to an environment variable. As of now, you will also need to edit `bin/deploy.sh` to add the new secret to the deployment and also add the secret to Google Secret Manager. Make a PR with this change and get it approved & merged.
1111

12-
Once this has been deployed, all you have to do is send a POST request to `https://product-eng-webhooks-vmrqv3f7nq-uw.a.run.app/event-notifier/v1` with a JSON payload in the format of the type `GenericEvent` defined in `@/types/index.ts`. Example:
12+
Once this has been deployed, all you have to do is send a POST request to `https://product-eng-webhooks-vmrqv3f7nq-uw.a.run.app/event-notifier/v1` with a JSON payload in the format of the type `GenericEvent` defined in `@/types/index.ts`. Currently, only Datadog and Slack messages are supported. Example:
1313

1414
```json
1515
{
1616
"source": "example-service", // This must match the mapping string you define in the EVENT_NOTIFIER_SECRETS obj
1717
"timestamp": 0,
18-
"service_name": "official_service_name",
19-
"data": {
20-
"title": "This is an Example Notification",
21-
"message": "Random text here",
22-
"tags": [
23-
"source:example-service", "sentry-region:all", "sentry-user:bob"
24-
],
25-
"misc": {},
26-
"channels": {
27-
"slack": ["C07EH2QGGQ5"],
28-
"jira": ["TEST"]
18+
"data": [
19+
{
20+
"type": "slack", // Basic Slack message
21+
"text": "Random text here",
22+
"channels": ["#aaaaaa"],
23+
// Optionally, include Slack Blocks
24+
"blocks": []
25+
}, {
26+
"type": "service_notification", // Slack message using service registry information
27+
"service_name": "eng_pipes_gh_notifications",
28+
"text": "Random text here",
29+
// Optionally, include Slack Blocks
30+
"blocks": []
31+
}, {
32+
"type": "datadog", // Datadog message
33+
"title": "This is an Example Notification",
34+
"text": "Random text here",
35+
"tags": ["source:example-service", "sentry-region:all", "sentry-user:bob"],
36+
"alertType": "info"
2937
}
30-
}
38+
]
3139
}
3240
```
3341

src/webhooks/generic-notifier/generic-notifier.test.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import testInvalidPayload from '@test/payloads/generic-notifier/testInvalidPayload.json';
22
import testPayload from '@test/payloads/generic-notifier/testPayload.json';
3+
import testServicePayload from '@test/payloads/generic-notifier/testServicePayload.json';
34
import { createNotifierRequest } from '@test/utils/createGenericMessageRequest';
45

56
import { buildServer } from '@/buildServer';
67
import { DATADOG_API_INSTANCE } from '@/config';
8+
import { GenericEvent, ServiceSlackMessage, SlackMessage } from '@/types';
79
import { bolt } from '@api/slack';
810

9-
import { messageSlack } from './generic-notifier';
11+
import { handleServiceSlackMessage, messageSlack } from './generic-notifier';
1012

1113
describe('generic messages webhook', function () {
1214
let fastify;
@@ -24,7 +26,10 @@ describe('generic messages webhook', function () {
2426
jest
2527
.spyOn(DATADOG_API_INSTANCE, 'createEvent')
2628
.mockImplementation(jest.fn());
27-
const response = await createNotifierRequest(fastify, testPayload);
29+
const response = await createNotifierRequest(
30+
fastify,
31+
testPayload as GenericEvent
32+
);
2833

2934
expect(response.statusCode).toBe(200);
3035
});
@@ -65,7 +70,7 @@ describe('generic messages webhook', function () {
6570

6671
it('writes to slack', async function () {
6772
const postMessageSpy = jest.spyOn(bolt.client.chat, 'postMessage');
68-
await messageSlack(testPayload);
73+
await messageSlack(testPayload.data[0] as SlackMessage);
6974
expect(postMessageSpy).toHaveBeenCalledTimes(1);
7075
const message = postMessageSpy.mock.calls[0][0];
7176
expect(message).toEqual({
@@ -75,18 +80,43 @@ describe('generic messages webhook', function () {
7580
});
7681
});
7782
});
83+
describe('handleServiceSlackMessage tests', function () {
84+
afterEach(function () {
85+
jest.clearAllMocks();
86+
});
87+
88+
it('writes to slack', async function () {
89+
const postMessageSpy = jest.spyOn(bolt.client.chat, 'postMessage');
90+
await handleServiceSlackMessage(
91+
testServicePayload.data[0] as ServiceSlackMessage
92+
);
93+
expect(postMessageSpy).toHaveBeenCalledTimes(1);
94+
const message = postMessageSpy.mock.calls[0][0];
95+
expect(message).toEqual({
96+
channel: 'feed-datdog',
97+
text: 'Random text here',
98+
unfurl_links: false,
99+
});
100+
});
101+
});
78102

79103
it('checks that slack msg is sent', async function () {
80104
const postMessageSpy = jest.spyOn(bolt.client.chat, 'postMessage');
81-
const response = await createNotifierRequest(fastify, testPayload);
105+
const response = await createNotifierRequest(
106+
fastify,
107+
testPayload as GenericEvent
108+
);
82109

83-
expect(postMessageSpy).toHaveBeenCalledTimes(1);
110+
expect(postMessageSpy).toHaveBeenCalledTimes(2);
84111

85112
expect(response.statusCode).toBe(200);
86113
});
87114
it('checks that dd msg is sent', async function () {
88115
const ddMessageSpy = jest.spyOn(DATADOG_API_INSTANCE, 'createEvent');
89-
const response = await createNotifierRequest(fastify, testPayload);
116+
const response = await createNotifierRequest(
117+
fastify,
118+
testPayload as GenericEvent
119+
);
90120

91121
expect(ddMessageSpy).toHaveBeenCalledTimes(1);
92122

0 commit comments

Comments
 (0)