feat(splunk-on-call-plugin): add api

This commit is contained in:
Remi
2021-02-04 23:41:34 +01:00
parent e6a9f7fec3
commit 5001b3f764
3 changed files with 337 additions and 0 deletions
+196
View File
@@ -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<SplunkOnCallApi>({
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<Incident[]> {
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/splunk-on-call/v1/incidents`;
const { incidents } = await this.getByUrl<IncidentsResponse>(url);
return incidents;
}
async getOnCallUsers(): Promise<OnCall[]> {
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/splunk-on-call/v1/oncall/current`;
const { teamsOnCall } = await this.getByUrl<OnCallsResponse>(url);
return teamsOnCall;
}
async acknowledgeIncident({
userName,
incidentNames,
}: PatchIncidentRequest): Promise<Response> {
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<Response> {
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<User[]> {
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/splunk-on-call/v2/user`;
const { users } = await this.getByUrl<ListUserResponse>(url);
return users;
}
async getEscalationPolicies(): Promise<EscalationPolicyInfo[]> {
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/splunk-on-call/v1/policies`;
const { policies } = await this.getByUrl<EscalationPolicyResponse>(url);
return policies;
}
async triggerAlarm({
summary,
details,
userName,
targets,
isMultiResponder,
}: TriggerAlarmRequest): Promise<Response> {
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<T>(url: string): Promise<T> {
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<Response> {
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;
}
}
+22
View File
@@ -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';
+119
View File
@@ -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<Incident[]>;
/**
* Fetches the list of users in an escalation policy.
*
*/
getOnCallUsers(): Promise<OnCall[]>;
/**
* Triggers an incident to whoever is on-call.
*/
triggerAlarm(request: TriggerAlarmRequest): Promise<Response>;
/**
* Resolves an incident.
*/
resolveIncident(request: PatchIncidentRequest): Promise<Response>;
/**
* Acknowledge an incident to whoever is on-call.
*/
acknowledgeIncident(request: PatchIncidentRequest): Promise<Response>;
/**
* Get a list of users for your organization.
*/
getUsers(): Promise<User[]>;
/**
* Get a list of users for your organization.
*/
getEscalationPolicies(): Promise<EscalationPolicyInfo[]>;
}
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;
};