-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ResponseOps][Stack Connectors] Opsgenie backend #142164
Merged
jonathan-buttner
merged 21 commits into
elastic:main
from
jonathan-buttner:opsgenie-connector
Oct 11, 2022
Merged
Changes from 16 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
33bcdcc
Starting opsgenie backend
jonathan-buttner 7413f2c
Merge branch 'main' of github.com:elastic/kibana into opsgenie-connector
jonathan-buttner dcb1fd9
Adding more integration tests
jonathan-buttner b6950fc
Updating readme
jonathan-buttner 0ba5a6c
Merge branch 'main' of github.com:elastic/kibana into opsgenie-connector
jonathan-buttner 0111502
Adding hash and alias
jonathan-buttner af4a94a
Fixing tests
jonathan-buttner 3eb263a
Switch to platinum for now
jonathan-buttner 854492b
Adding server side translations
jonathan-buttner a08975b
Merge branch 'main' of github.com:elastic/kibana into opsgenie-connector
jonathan-buttner 9892a2d
Fixing merge issues
jonathan-buttner 40bbcc4
Merge branch 'main' of github.com:elastic/kibana into opsgenie-connector
jonathan-buttner c79afdc
Fixing file location error
jonathan-buttner b2b7e53
Fixing test
jonathan-buttner d3012f9
Addressing feedback
jonathan-buttner cf442ad
Merge branch 'main' of github.com:elastic/kibana into opsgenie-connector
jonathan-buttner 22d32ad
Removing details validation
jonathan-buttner 13431f0
Merge branch 'main' into opsgenie-connector
kibanamachine b233bbb
Merge branch 'main' into opsgenie-connector
kibanamachine fef41ba
Fixing close alert alias bug
jonathan-buttner 5483dbb
Merge branch 'opsgenie-connector' of github.com:jonathan-buttner/kiba…
jonathan-buttner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,8 @@ export { | |
} from './webhook'; | ||
export type { ActionParamsType as WebhookActionParams } from './webhook'; | ||
|
||
export { getOpsgenieConnectorType, OpsgenieConnectorTypeId } from './opsgenie'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll add the |
||
|
||
export { | ||
getConnectorType as getXmattersConnectorType, | ||
ConnectorTypeId as XmattersConnectorTypeId, | ||
|
135 changes: 135 additions & 0 deletions
135
x-pack/plugins/stack_connectors/server/connector_types/stack/opsgenie/connector.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import axios, { AxiosInstance } from 'axios'; | ||
import crypto from 'crypto'; | ||
import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; | ||
import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; | ||
import { actionsMock } from '@kbn/actions-plugin/server/mocks'; | ||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; | ||
import { MockedLogger } from '@kbn/logging-mocks'; | ||
import { OpsgenieConnectorTypeId } from '.'; | ||
import { OpsgenieConnector } from './connector'; | ||
import * as utils from '@kbn/actions-plugin/server/lib/axios_utils'; | ||
|
||
jest.mock('axios'); | ||
|
||
jest.mock('@kbn/actions-plugin/server/lib/axios_utils', () => { | ||
const originalUtils = jest.requireActual('@kbn/actions-plugin/server/lib/axios_utils'); | ||
return { | ||
...originalUtils, | ||
request: jest.fn(), | ||
}; | ||
}); | ||
|
||
const axiosMock = axios as jest.Mocked<typeof axios>; | ||
const requestMock = utils.request as jest.Mock; | ||
|
||
describe('OpsgenieConnector', () => { | ||
const axiosInstanceMock = jest.fn(); | ||
|
||
let connector: OpsgenieConnector; | ||
let mockedActionsConfig: jest.Mocked<ActionsConfigurationUtilities>; | ||
let logger: MockedLogger; | ||
let services: ReturnType<typeof actionsMock.createServices>; | ||
|
||
const defaultCreateAlertExpect = { | ||
method: 'post', | ||
url: 'https://example.com/v2/alerts', | ||
headers: { Authorization: 'GenieKey 123', 'Content-Type': 'application/json' }, | ||
}; | ||
|
||
const createCloseAlertExpect = (alias: string) => ({ | ||
method: 'post', | ||
url: `https://example.com/v2/alerts/${alias}/close?identifierType=alias`, | ||
headers: { Authorization: 'GenieKey 123', 'Content-Type': 'application/json' }, | ||
}); | ||
|
||
const ignoredRequestFields = { | ||
axios: expect.anything(), | ||
configurationUtilities: expect.anything(), | ||
logger: expect.anything(), | ||
}; | ||
|
||
beforeEach(() => { | ||
jest.resetAllMocks(); | ||
jest.clearAllMocks(); | ||
requestMock.mockReturnValue({ data: { took: 5, requestId: '123', result: 'ok' } }); | ||
axiosMock.create.mockImplementation(() => { | ||
return axiosInstanceMock as unknown as AxiosInstance; | ||
}); | ||
|
||
logger = loggingSystemMock.createLogger(); | ||
services = actionsMock.createServices(); | ||
mockedActionsConfig = actionsConfigMock.create(); | ||
|
||
connector = new OpsgenieConnector({ | ||
configurationUtilities: mockedActionsConfig, | ||
config: { apiUrl: 'https://example.com' }, | ||
connector: { id: '1', type: OpsgenieConnectorTypeId }, | ||
secrets: { apiKey: '123' }, | ||
logger, | ||
services, | ||
}); | ||
}); | ||
|
||
it('calls request with the correct arguments for creating an alert', async () => { | ||
await connector.createAlert({ message: 'hello' }); | ||
|
||
expect(requestMock.mock.calls[0][0]).toEqual({ | ||
data: { message: 'hello' }, | ||
...ignoredRequestFields, | ||
...defaultCreateAlertExpect, | ||
}); | ||
}); | ||
|
||
it('calls request without modifying the alias when it is less than 512 characters when creating an alert', async () => { | ||
await connector.createAlert({ message: 'hello', alias: '111' }); | ||
|
||
expect(requestMock.mock.calls[0][0]).toEqual({ | ||
...ignoredRequestFields, | ||
...defaultCreateAlertExpect, | ||
data: { message: 'hello', alias: '111' }, | ||
}); | ||
}); | ||
|
||
it('calls request without modifying the alias when it is equal to 512 characters when creating an alert', async () => { | ||
const alias = 'a'.repeat(512); | ||
await connector.createAlert({ message: 'hello', alias }); | ||
|
||
expect(requestMock.mock.calls[0][0]).toEqual({ | ||
...ignoredRequestFields, | ||
...defaultCreateAlertExpect, | ||
data: { message: 'hello', alias }, | ||
}); | ||
}); | ||
|
||
it('calls request with the sha256 hash of the alias when it is greater than 512 characters when creating an alert', async () => { | ||
const alias = 'a'.repeat(513); | ||
|
||
const hasher = crypto.createHash('sha256'); | ||
const sha256Hash = hasher.update(alias); | ||
|
||
await connector.createAlert({ message: 'hello', alias }); | ||
|
||
expect(requestMock.mock.calls[0][0]).toEqual({ | ||
...ignoredRequestFields, | ||
...defaultCreateAlertExpect, | ||
data: { message: 'hello', alias: sha256Hash.digest('hex') }, | ||
}); | ||
}); | ||
|
||
it('calls request with the correct arguments for closing an alert', async () => { | ||
await connector.closeAlert({ user: 'sam', alias: '111' }); | ||
|
||
expect(requestMock.mock.calls[0][0]).toEqual({ | ||
...ignoredRequestFields, | ||
...createCloseAlertExpect('111'), | ||
data: { user: 'sam' }, | ||
}); | ||
}); | ||
}); |
104 changes: 104 additions & 0 deletions
104
x-pack/plugins/stack_connectors/server/connector_types/stack/opsgenie/connector.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import crypto from 'crypto'; | ||
import { ServiceParams, SubActionConnector } from '@kbn/actions-plugin/server'; | ||
import { AxiosError } from 'axios'; | ||
import { CloseAlertParamsSchema, CreateAlertParamsSchema, Response } from './schema'; | ||
import { CloseAlertParams, Config, CreateAlertParams, Secrets } from './types'; | ||
import * as i18n from './translations'; | ||
|
||
interface ErrorSchema { | ||
message?: string; | ||
errors?: { | ||
message?: string; | ||
}; | ||
} | ||
|
||
export class OpsgenieConnector extends SubActionConnector<Config, Secrets> { | ||
constructor(params: ServiceParams<Config, Secrets>) { | ||
super(params); | ||
|
||
this.registerSubAction({ | ||
method: this.createAlert.name, | ||
name: 'createAlert', | ||
schema: CreateAlertParamsSchema, | ||
}); | ||
|
||
this.registerSubAction({ | ||
method: this.closeAlert.name, | ||
name: 'closeAlert', | ||
schema: CloseAlertParamsSchema, | ||
}); | ||
} | ||
|
||
public getResponseErrorMessage(error: AxiosError<ErrorSchema>) { | ||
return `Message: ${ | ||
error.response?.data.errors?.message ?? | ||
error.response?.data.message ?? | ||
error.message ?? | ||
i18n.UNKNOWN_ERROR | ||
}`; | ||
} | ||
|
||
public async createAlert(params: CreateAlertParams) { | ||
const res = await this.request({ | ||
method: 'post', | ||
url: this.concatPathToURL('v2/alerts').toString(), | ||
data: { ...params, ...OpsgenieConnector.createAliasObj(params.alias) }, | ||
headers: this.createHeaders(), | ||
responseSchema: Response, | ||
}); | ||
|
||
return res.data; | ||
} | ||
|
||
private static createAliasObj(alias?: string) { | ||
if (!alias) { | ||
return {}; | ||
} | ||
|
||
// opsgenie v2 requires that the alias length be no more than 512 characters | ||
// see their docs for more details https://docs.opsgenie.com/docs/alert-api#create-alert | ||
if (alias.length <= 512) { | ||
return { alias }; | ||
} | ||
|
||
// To give preference to avoiding collisions we're using sha256 over of md5 but we are compromising on speed a bit here | ||
const hasher = crypto.createHash('sha256'); | ||
const sha256Hash = hasher.update(alias); | ||
|
||
return { alias: sha256Hash.digest('hex') }; | ||
} | ||
|
||
private createHeaders() { | ||
return { Authorization: `GenieKey ${this.secrets.apiKey}` }; | ||
} | ||
|
||
public async closeAlert(params: CloseAlertParams) { | ||
const fullURL = this.concatPathToURL(`v2/alerts/${params.alias}/close`); | ||
fullURL.searchParams.set('identifierType', 'alias'); | ||
|
||
const { alias, ...paramsWithoutAlias } = params; | ||
|
||
const res = await this.request({ | ||
method: 'post', | ||
url: fullURL.toString(), | ||
data: paramsWithoutAlias, | ||
headers: this.createHeaders(), | ||
responseSchema: Response, | ||
}); | ||
|
||
return res.data; | ||
} | ||
|
||
private concatPathToURL(path: string) { | ||
const fullURL = new URL(path, this.config.apiUrl); | ||
|
||
return fullURL; | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
x-pack/plugins/stack_connectors/server/connector_types/stack/opsgenie/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { | ||
AlertingConnectorFeatureId, | ||
SecurityConnectorFeatureId, | ||
UptimeConnectorFeatureId, | ||
} from '@kbn/actions-plugin/common'; | ||
import { urlAllowListValidator } from '@kbn/actions-plugin/server'; | ||
import { | ||
SubActionConnectorType, | ||
ValidatorType, | ||
} from '@kbn/actions-plugin/server/sub_action_framework/types'; | ||
import { OpsgenieConnector } from './connector'; | ||
import { ConfigSchema, SecretsSchema } from './schema'; | ||
import { Config, Secrets } from './types'; | ||
import * as i18n from './translations'; | ||
|
||
export const OpsgenieConnectorTypeId = '.opsgenie'; | ||
|
||
export const getOpsgenieConnectorType = (): SubActionConnectorType<Config, Secrets> => { | ||
return { | ||
Service: OpsgenieConnector, | ||
minimumLicenseRequired: 'platinum', | ||
name: i18n.OPSGENIE_NAME, | ||
id: OpsgenieConnectorTypeId, | ||
schema: { config: ConfigSchema, secrets: SecretsSchema }, | ||
validators: [{ type: ValidatorType.CONFIG, validator: urlAllowListValidator('apiUrl') }], | ||
supportedFeatureIds: [ | ||
AlertingConnectorFeatureId, | ||
UptimeConnectorFeatureId, | ||
SecurityConnectorFeatureId, | ||
], | ||
}; | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was a whitespace change 🤔