From b208a7f1c91ac54c909a298b5b8269c04e88a0b5 Mon Sep 17 00:00:00 2001 From: Brandon Shien Date: Mon, 25 Nov 2024 13:44:30 -0800 Subject: [PATCH] Added check to events-to-s3 for label canary Signed-off-by: Brandon Shien --- configs/operations/github-events-to-s3.yml | 4 + package-lock.json | 4 +- package.json | 2 +- src/call/github-label-canary-monitor.ts | 57 ++++++++ test/call/github-label-canary-monitor.test.ts | 124 ++++++++++++++++++ 5 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 src/call/github-label-canary-monitor.ts create mode 100644 test/call/github-label-canary-monitor.test.ts diff --git a/configs/operations/github-events-to-s3.yml b/configs/operations/github-events-to-s3.yml index e079831..705d5c3 100644 --- a/configs/operations/github-events-to-s3.yml +++ b/configs/operations/github-events-to-s3.yml @@ -5,5 +5,9 @@ events: - all tasks: + - name: Github Label Canary Monitor + call: github-label-canary-monitor@default + args: + metricName: AutomationApp_EventDataLake - name: Github Events To S3 Operation call: github-events-to-s3@default diff --git a/package-lock.json b/package-lock.json index 75bb6ab..d28fcf6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "opensearch-automation-app", - "version": "0.3.5", + "version": "0.3.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "opensearch-automation-app", - "version": "0.3.5", + "version": "0.3.6", "dependencies": { "@aws-sdk/client-cloudwatch": "^3.664.0", "@aws-sdk/client-opensearch": "^3.658.1", diff --git a/package.json b/package.json index 1fa71a1..d429995 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opensearch-automation-app", - "version": "0.3.5", + "version": "0.3.6", "description": "An Automation App that handles all your GitHub Repository Activities", "author": "Peter Zhu", "homepage": "https://github.com/opensearch-project/automation-app", diff --git a/src/call/github-label-canary-monitor.ts b/src/call/github-label-canary-monitor.ts new file mode 100644 index 0000000..499f5d0 --- /dev/null +++ b/src/call/github-label-canary-monitor.ts @@ -0,0 +1,57 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +// Name : githubLabelCanaryMonitor +// Description : Handle canary event by sending CloudWatch Metrics for monitoring purposes +// Arguments : +// - metricName: (string) The name of the CloudWatch Metric you want to send data to. + +import { Probot } from 'probot'; +import { CloudWatchClient, PutMetricDataCommand } from '@aws-sdk/client-cloudwatch'; + +export interface LabelCanaryMonitorParams { + metricName: string; +} + +export default async function githubLabelCanaryMonitor(app: Probot, context: any, { metricName }: LabelCanaryMonitorParams): Promise { + // Removed validateResourceConfig to let this function listen on all repos, and filter for only the repos that are public. + // This is done so when a new repo is made public, this app can automatically start processing its events. + // + // This is only for the s3 data lake specific case, everything else should still specify repos required to be listened in resource config. + // + // if (!(await validateResourceConfig(app, context, resource))) return; + // + const repoName = context.payload.repository?.name; + if (context.payload.repository?.private === false) { + // Handle canary event for monitoring purposes + if (repoName === 'opensearch-metrics' && context.name === 'label' && context.payload.label?.name === 's3-data-lake-app-canary-label') { + // Ignore if label was created + if (context.payload.action === 'deleted') { + try { + const cloudWatchClient = new CloudWatchClient({ region: String(process.env.REGION) }); + const putMetricDataCommand = new PutMetricDataCommand({ + Namespace: 'GitHubLabelCanary', + MetricData: [ + { + MetricName: metricName, + Value: 1, + Unit: 'Count', + }, + ], + }); + await cloudWatchClient.send(putMetricDataCommand); + app.log.info(`CloudWatch metric for monitoring published.${metricName}`); + } catch (error) { + app.log.error(`Error Publishing CloudWatch metric for monitoring : ${error}`); + } + } + return; // In the future, add `exit` right here to prevent subsequent tasks from running + } + } +} diff --git a/test/call/github-label-canary-monitor.test.ts b/test/call/github-label-canary-monitor.test.ts new file mode 100644 index 0000000..1d2b1a7 --- /dev/null +++ b/test/call/github-label-canary-monitor.test.ts @@ -0,0 +1,124 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +import { Logger, Probot } from 'probot'; +import githubLabelCanaryMonitor, { LabelCanaryMonitorParams } from '../../src/call/github-label-canary-monitor'; +import { CloudWatchClient, PutMetricDataCommand } from '@aws-sdk/client-cloudwatch'; + +jest.mock('@aws-sdk/client-cloudwatch'); + +describe('githubEventsToS3', () => { + let app: Probot; + let context: any; + let mockCloudWatchClient: any; + let args: LabelCanaryMonitorParams; + + beforeEach(() => { + args = { + metricName: 'testMetric', + }; + + app = new Probot({ appId: 1, secret: 'test', privateKey: 'test' }); + app.log = { + info: jest.fn(), + error: jest.fn(), + } as unknown as Logger; + + context = { + name: 'name', + id: 'id', + payload: { + repository: { + name: 'repo', + owner: { login: 'org' }, + private: false, + }, + }, + }; + + mockCloudWatchClient = { + send: jest.fn(), + }; + (CloudWatchClient as jest.Mock).mockImplementation(() => mockCloudWatchClient); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should publish CloudWatch metric if event is label canary', async () => { + context = { + name: 'label', + id: 'id', + payload: { + action: 'deleted', + label: { + name: 's3-data-lake-app-canary-label', + }, + repository: { + name: 'opensearch-metrics', + private: false, + }, + }, + }; + + mockCloudWatchClient.send.mockResolvedValue({}); + + await githubLabelCanaryMonitor(app, context, args); + + expect(mockCloudWatchClient.send).toHaveBeenCalledWith(expect.any(PutMetricDataCommand)); + expect(app.log.info).toHaveBeenCalledWith('CloudWatch metric for monitoring published.testMetric'); + }); + + it('should not publish CloudWatch metric if event is not label canary', async () => { + context = { + name: 'label', + id: 'id', + payload: { + label: { + name: 'normal-label', + }, + repository: { + name: 'opensearch-metrics', + private: false, + }, + }, + }; + + mockCloudWatchClient.send.mockResolvedValue({}); + + await githubLabelCanaryMonitor(app, context, args); + + expect(mockCloudWatchClient.send).not.toHaveBeenCalledWith(expect.any(PutMetricDataCommand)); + expect(app.log.info).not.toHaveBeenCalledWith('CloudWatch metric for monitoring published.testMetric'); + }); + + it('should log an error if CloudWatch metric publishing fails', async () => { + context = { + name: 'label', + id: 'id', + payload: { + action: 'deleted', + label: { + name: 's3-data-lake-app-canary-label', + }, + repository: { + name: 'opensearch-metrics', + private: false, + }, + }, + }; + + mockCloudWatchClient.send.mockRejectedValue(new Error('CloudWatch error')); + + await githubLabelCanaryMonitor(app, context, args); + + expect(app.log.error).toHaveBeenCalledWith('Error Publishing CloudWatch metric for monitoring : Error: CloudWatch error'); + }); +});