diff --git a/plugins/splunk-on-call/src/api/client.ts b/plugins/splunk-on-call/src/api/client.ts new file mode 100644 index 0000000000..09c3b6cf93 --- /dev/null +++ b/plugins/splunk-on-call/src/api/client.ts @@ -0,0 +1,196 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createApiRef, DiscoveryApi, ConfigApi } from '@backstage/core'; +import { + Incident, + OnCall, + User, + EscalationPolicyInfo, +} from '../components/types'; +import { + SplunkOnCallApi, + TriggerAlarmRequest, + IncidentsResponse, + OnCallsResponse, + ClientApiConfig, + RequestOptions, + ListUserResponse, + EscalationPolicyResponse, + PatchIncidentRequest, +} from './types'; + +export class UnauthorizedError extends Error {} + +export const splunkOnCallApiRef = createApiRef({ + id: 'plugin.splunk-on-call.api', + description: 'Used to fetch data from SplunkOnCall API', +}); + +export class SplunkOnCallClient implements SplunkOnCallApi { + static fromConfig(configApi: ConfigApi, discoveryApi: DiscoveryApi) { + const usernameFromConfig: string | null = + configApi.getOptionalString('splunkoncall.username') || 'ayshiff'; + return new SplunkOnCallClient({ + username: usernameFromConfig, + discoveryApi, + }); + } + constructor(private readonly config: ClientApiConfig) {} + + async getIncidents(): Promise { + const url = `${await this.config.discoveryApi.getBaseUrl( + 'proxy', + )}/splunk-on-call/v1/incidents`; + + const { incidents } = await this.getByUrl(url); + + return incidents; + } + + async getOnCallUsers(): Promise { + const url = `${await this.config.discoveryApi.getBaseUrl( + 'proxy', + )}/splunk-on-call/v1/oncall/current`; + const { teamsOnCall } = await this.getByUrl(url); + + return teamsOnCall; + } + + async acknowledgeIncident({ + userName, + incidentNames, + }: PatchIncidentRequest): Promise { + const options = { + method: 'PATCH', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + userName: this.config.username || userName, + incidentNames, + }), + }; + + const url = `${await this.config.discoveryApi.getBaseUrl( + 'proxy', + )}/splunk-on-call/v1/incidents/ack`; + + return this.request(url, options); + } + + async resolveIncident({ + userName, + incidentNames, + }: PatchIncidentRequest): Promise { + const options = { + method: 'PATCH', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + userName: this.config.username || userName, + incidentNames, + }), + }; + + const url = `${await this.config.discoveryApi.getBaseUrl( + 'proxy', + )}/splunk-on-call/v1/incidents/resolve`; + + return this.request(url, options); + } + + async getUsers(): Promise { + const url = `${await this.config.discoveryApi.getBaseUrl( + 'proxy', + )}/splunk-on-call/v2/user`; + const { users } = await this.getByUrl(url); + + return users; + } + + async getEscalationPolicies(): Promise { + const url = `${await this.config.discoveryApi.getBaseUrl( + 'proxy', + )}/splunk-on-call/v1/policies`; + const { policies } = await this.getByUrl(url); + + return policies; + } + + async triggerAlarm({ + summary, + details, + userName, + targets, + isMultiResponder, + }: TriggerAlarmRequest): Promise { + const body = JSON.stringify({ + summary, + details, + userName: this.config.username || userName, + targets, + isMultiResponder, + }); + + const options = { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body, + }; + + const url = `${await this.config.discoveryApi.getBaseUrl( + 'proxy', + )}/splunk-on-call/v1/incidents`; + + return this.request(url, options); + } + + private async getByUrl(url: string): Promise { + const options = { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }; + const response = await this.request(url, options); + + return response.json(); + } + + private async request( + url: string, + options: RequestOptions, + ): Promise { + const response = await fetch(url, options); + if (response.status === 401) { + throw new UnauthorizedError(); + } + if (!response.ok) { + const payload = await response.json(); + const errors = payload.errors.map((error: string) => error).join(' '); + const message = `Request failed with ${response.status}, ${errors}`; + throw new Error(message); + } + return response; + } +} diff --git a/plugins/splunk-on-call/src/api/index.ts b/plugins/splunk-on-call/src/api/index.ts new file mode 100644 index 0000000000..1e4056fbc0 --- /dev/null +++ b/plugins/splunk-on-call/src/api/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { + SplunkOnCallClient, + splunkOnCallApiRef, + UnauthorizedError, +} from './client'; +export type { SplunkOnCallApi } from './types'; diff --git a/plugins/splunk-on-call/src/api/types.ts b/plugins/splunk-on-call/src/api/types.ts new file mode 100644 index 0000000000..cc3b1a1e8c --- /dev/null +++ b/plugins/splunk-on-call/src/api/types.ts @@ -0,0 +1,119 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + EscalationPolicyInfo, + Incident, + OnCall, + Service, + User, +} from '../components/types'; +import { DiscoveryApi } from '@backstage/core'; + +export enum TargetType { + User = 'User', + EscalationPolicy = 'EscalationPolicy', +} + +export type IncidentTarget = { + type: TargetType; + slug: string; +}; + +export type TriggerAlarmRequest = { + targets: IncidentTarget[]; + details: string; + summary: string; + userName: string; + isMultiResponder?: boolean; +}; + +export interface SplunkOnCallApi { + /** + * Fetches a list of incidents + * + */ + getIncidents(): Promise; + + /** + * Fetches the list of users in an escalation policy. + * + */ + getOnCallUsers(): Promise; + + /** + * Triggers an incident to whoever is on-call. + */ + triggerAlarm(request: TriggerAlarmRequest): Promise; + + /** + * Resolves an incident. + */ + resolveIncident(request: PatchIncidentRequest): Promise; + + /** + * Acknowledge an incident to whoever is on-call. + */ + acknowledgeIncident(request: PatchIncidentRequest): Promise; + + /** + * Get a list of users for your organization. + */ + getUsers(): Promise; + + /** + * Get a list of users for your organization. + */ + getEscalationPolicies(): Promise; +} + +export type PatchIncidentRequest = { + userName: string; + incidentNames: string[]; + message?: string; +}; + +export type EscalationPolicyResponse = { + policies: EscalationPolicyInfo[]; +}; + +export type ListUserResponse = { + users: User[]; + _selfUrl?: string; +}; + +export type ServicesResponse = { + services: Service[]; +}; + +export type IncidentsResponse = { + incidents: Incident[]; +}; + +export type OnCallsResponse = { + teamsOnCall: OnCall[]; +}; + +export type ClientApiConfig = { + username: string | null; + discoveryApi: DiscoveryApi; +}; + +export type RequestOptions = { + method: string; + headers: HeadersInit; + body?: BodyInit; +};