[IRM] - changes to get incident working in IRM plugin (#4987)
Required for https://github.com/grafana/irm/pull/69
This commit is contained in:
parent
df6f7af8a5
commit
a0a5482a85
12 changed files with 65 additions and 49 deletions
|
|
@ -4,19 +4,14 @@
|
||||||
[run]
|
[run]
|
||||||
init_cmds = [
|
init_cmds = [
|
||||||
["mage", "-v", "build:debug"],
|
["mage", "-v", "build:debug"],
|
||||||
["mage", "-v" , "reloadPlugin"],
|
|
||||||
]
|
]
|
||||||
watch_all = true
|
watch_all = true
|
||||||
follow_symlinks = false
|
follow_symlinks = false
|
||||||
ignore = [".git", "node_modules", "dist"]
|
ignore = [".git", "node_modules", "dist"]
|
||||||
ignore_files = ["mage_output_file.go"]
|
ignore_files = ["mage_output_file.go"]
|
||||||
watch_dirs = [
|
watch_dirs = ["pkg"]
|
||||||
"pkg",
|
|
||||||
# "src",
|
|
||||||
]
|
|
||||||
watch_exts = [".go", ".json"]
|
watch_exts = [".go", ".json"]
|
||||||
build_delay = 2000
|
build_delay = 2000
|
||||||
cmds = [
|
cmds = [
|
||||||
["mage", "-v", "build:debug"],
|
["mage", "-v", "build:debug"],
|
||||||
["mage", "-v" , "reloadPlugin"],
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
module github.com/grafana-labs/grafana-oncall-app
|
module github.com/grafana/grafana-oncall-app
|
||||||
|
|
||||||
go 1.21.5
|
go 1.21.5
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/grafana-labs/grafana-oncall-app/pkg/plugin"
|
"github.com/grafana/grafana-oncall-app/pkg/plugin"
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend/app"
|
"github.com/grafana/grafana-plugin-sdk-go/backend/app"
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||||
)
|
)
|
||||||
|
|
@ -16,7 +16,7 @@ func main() {
|
||||||
// argument. This factory will be automatically called on incoming request
|
// argument. This factory will be automatically called on incoming request
|
||||||
// from Grafana to create different instances of `App` (per plugin
|
// from Grafana to create different instances of `App` (per plugin
|
||||||
// ID).
|
// ID).
|
||||||
if err := app.Manage("grafana-oncall-app", plugin.NewApp, app.ManageOpts{}); err != nil {
|
if err := app.Manage("grafana-oncall-app", plugin.NewInstance, app.ManageOpts{}); err != nil {
|
||||||
log.DefaultLogger.Error(err.Error())
|
log.DefaultLogger.Error(err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,15 +38,9 @@ type App struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApp creates a new example *App instance.
|
// NewApp creates a new example *App instance.
|
||||||
func NewApp(ctx context.Context, settings backend.AppInstanceSettings) (instancemgmt.Instance, error) {
|
func NewApp(ctx context.Context, settings backend.AppInstanceSettings) (*App, error) {
|
||||||
var app App
|
var app App
|
||||||
|
|
||||||
// Use a httpadapter (provided by the SDK) for resource calls. This allows us
|
|
||||||
// to use a *http.ServeMux for resource calls, so we can map multiple routes
|
|
||||||
// to CallResource without having to implement extra logic.
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
app.registerRoutes(mux)
|
|
||||||
app.CallResourceHandler = httpadapter.New(mux)
|
|
||||||
app.OnCallSyncCache = &OnCallSyncCache{}
|
app.OnCallSyncCache = &OnCallSyncCache{}
|
||||||
app.OnCallSettingsCache = &OnCallSettingsCache{}
|
app.OnCallSettingsCache = &OnCallSettingsCache{}
|
||||||
app.OnCallUserCache = NewOnCallUserCache()
|
app.OnCallUserCache = NewOnCallUserCache()
|
||||||
|
|
@ -66,6 +60,25 @@ func NewApp(ctx context.Context, settings backend.AppInstanceSettings) (instance
|
||||||
return &app, nil
|
return &app, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewInstance creates a new example *Instance instance.
|
||||||
|
func NewInstance(ctx context.Context, settings backend.AppInstanceSettings) (instancemgmt.Instance, error) {
|
||||||
|
app, err := NewApp(ctx, settings)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.DefaultLogger.Error("Error creating new app", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a httpadapter (provided by the SDK) for resource calls. This allows us
|
||||||
|
// to use a *http.ServeMux for resource calls, so we can map multiple routes
|
||||||
|
// to CallResource without having to implement extra logic.
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
app.registerRoutes(mux)
|
||||||
|
app.CallResourceHandler = httpadapter.New(mux)
|
||||||
|
|
||||||
|
return app, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Dispose here tells plugin SDK that plugin wants to clean up resources when a new instance
|
// Dispose here tells plugin SDK that plugin wants to clean up resources when a new instance
|
||||||
// created.
|
// created.
|
||||||
func (a *App) Dispose() {
|
func (a *App) Dispose() {
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
|
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
|
||||||
"net/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type OnCallDebugStats struct {
|
type OnCallDebugStats struct {
|
||||||
|
|
@ -47,7 +48,7 @@ func (a *App) handleDebugSync(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
onCallSync, err := a.GetSyncData(req.Context(), onCallPluginSettings)
|
onCallSync, err := a.GetSyncData(onCallPluginSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.DefaultLogger.Error("Error getting sync data", "error", err)
|
log.DefaultLogger.Error("Error getting sync data", "error", err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ func (a *App) handleInstall(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
onCallSync, err := a.GetSyncData(req.Context(), onCallPluginSettings)
|
onCallSync, err := a.GetSyncData(onCallPluginSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.DefaultLogger.Error("Error getting sync data", "error", err)
|
log.DefaultLogger.Error("Error getting sync data", "error", err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ func afterRequest(handler http.Handler, afterFunc func(*responseWriter, *http.Re
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) handleInternalApi(w http.ResponseWriter, req *http.Request) {
|
func (a *App) HandleInternalApi(w http.ResponseWriter, req *http.Request) {
|
||||||
a.ProxyRequestToOnCall(w, req, "api/internal/v1/")
|
a.ProxyRequestToOnCall(w, req, "api/internal/v1/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,10 +121,10 @@ func (a *App) handleLegacyInstall(w *responseWriter, req *http.Request) {
|
||||||
// registerRoutes takes a *http.ServeMux and registers some HTTP handlers.
|
// registerRoutes takes a *http.ServeMux and registers some HTTP handlers.
|
||||||
func (a *App) registerRoutes(mux *http.ServeMux) {
|
func (a *App) registerRoutes(mux *http.ServeMux) {
|
||||||
mux.HandleFunc("/plugin/install", a.handleInstall)
|
mux.HandleFunc("/plugin/install", a.handleInstall)
|
||||||
mux.HandleFunc("/plugin/status", a.handleStatus)
|
mux.HandleFunc("/plugin/status", a.HandleStatus)
|
||||||
mux.HandleFunc("/plugin/sync", a.handleSync)
|
mux.HandleFunc("/plugin/sync", a.HandleSync)
|
||||||
|
|
||||||
mux.Handle("/plugin/self-hosted/install", afterRequest(http.HandlerFunc(a.handleInternalApi), a.handleLegacyInstall))
|
mux.Handle("/plugin/self-hosted/install", afterRequest(http.HandlerFunc(a.HandleInternalApi), a.handleLegacyInstall))
|
||||||
|
|
||||||
// Disable debug endpoints
|
// Disable debug endpoints
|
||||||
//mux.HandleFunc("/debug/user", a.handleDebugUser)
|
//mux.HandleFunc("/debug/user", a.handleDebugUser)
|
||||||
|
|
@ -134,5 +134,5 @@ func (a *App) registerRoutes(mux *http.ServeMux) {
|
||||||
//mux.HandleFunc("/debug/stats", a.handleDebugStats)
|
//mux.HandleFunc("/debug/stats", a.handleDebugStats)
|
||||||
//mux.HandleFunc("/debug/unlock", a.handleDebugUnlock)
|
//mux.HandleFunc("/debug/unlock", a.handleDebugUnlock)
|
||||||
|
|
||||||
mux.HandleFunc("/", a.handleInternalApi)
|
mux.HandleFunc("/", a.HandleInternalApi)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ package plugin
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
)
|
)
|
||||||
|
|
||||||
// mockCallResourceResponseSender implements backend.CallResourceResponseSender
|
// mockCallResourceResponseSender implements backend.CallResourceResponseSender
|
||||||
|
|
@ -23,7 +24,7 @@ func (s *mockCallResourceResponseSender) Send(response *backend.CallResourceResp
|
||||||
// This ensures the httpadapter for CallResource works correctly.
|
// This ensures the httpadapter for CallResource works correctly.
|
||||||
func TestCallResource(t *testing.T) {
|
func TestCallResource(t *testing.T) {
|
||||||
// Initialize app
|
// Initialize app
|
||||||
inst, err := NewApp(context.Background(), backend.AppInstanceSettings{})
|
inst, err := NewInstance(context.Background(), backend.AppInstanceSettings{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("new app: %s", err)
|
t.Fatalf("new app: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -317,32 +317,29 @@ func (a *App) SaveOnCallSettings(settings *OnCallPluginSettings) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetSyncData(ctx context.Context, settings *OnCallPluginSettings) (*OnCallSync, error) {
|
func (a *App) GetSyncData(pluginSettings *OnCallPluginSettings) (*OnCallSync, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
startGetSyncData := time.Now()
|
startGetSyncData := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
elapsed := time.Since(startGetSyncData)
|
elapsed := time.Since(startGetSyncData)
|
||||||
log.DefaultLogger.Info("GetSyncData", "time", elapsed.Milliseconds())
|
log.DefaultLogger.Info("GetSyncData", "time", elapsed.Milliseconds())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
onCallPluginSettings, err := a.OnCallSettingsFromContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting settings from context = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
onCallSync := OnCallSync{
|
onCallSync := OnCallSync{
|
||||||
Settings: *settings,
|
Settings: *pluginSettings,
|
||||||
}
|
}
|
||||||
onCallSync.Users, err = a.GetAllUsersWithPermissions(onCallPluginSettings)
|
onCallSync.Users, err = a.GetAllUsersWithPermissions(pluginSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting users = %v", err)
|
return nil, fmt.Errorf("error getting users = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
onCallSync.Teams, err = a.GetAllTeams(onCallPluginSettings)
|
onCallSync.Teams, err = a.GetAllTeams(pluginSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting teams = %v", err)
|
return nil, fmt.Errorf("error getting teams = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
teamMembers, err := a.GetAllTeamMembers(onCallPluginSettings, onCallSync.Teams)
|
teamMembers, err := a.GetAllTeamMembers(pluginSettings, onCallSync.Teams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting team members = %v", err)
|
return nil, fmt.Errorf("error getting team members = %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,7 @@ func (a *App) ValidateOnCallStatus(ctx context.Context, settings *OnCallPluginSe
|
||||||
return &status, nil
|
return &status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) handleStatus(w http.ResponseWriter, req *http.Request) {
|
func (a *App) HandleStatus(w http.ResponseWriter, req *http.Request) {
|
||||||
if req.Method != http.MethodGet {
|
if req.Method != http.MethodGet {
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,14 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OnCallSyncCache struct {
|
type OnCallSyncCache struct {
|
||||||
|
|
@ -38,7 +39,7 @@ func (oc *OnCallSyncCache) UnlockAfterDelay(delay time.Duration) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) handleSync(w http.ResponseWriter, req *http.Request) {
|
func (a *App) HandleSync(w http.ResponseWriter, req *http.Request) {
|
||||||
if req.Method != http.MethodPost {
|
if req.Method != http.MethodPost {
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
|
|
@ -122,7 +123,7 @@ func (a *App) makeSyncRequest(ctx context.Context, forceSend bool) error {
|
||||||
return fmt.Errorf("error getting settings from context: %v ", err)
|
return fmt.Errorf("error getting settings from context: %v ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
onCallSync, err := a.GetSyncData(ctx, onCallPluginSettings)
|
onCallSync, err := a.GetSyncData(onCallPluginSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error getting sync data: %v", err)
|
return fmt.Errorf("error getting sync data: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
import { OnCallPluginMetaJSONData } from 'app-types';
|
import { OnCallPluginMetaJSONData } from 'app-types';
|
||||||
import { getPluginId } from 'helpers/consts';
|
import { getPluginId, PluginId } from 'helpers/consts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ApiAuthKeyDTO,
|
ApiAuthKeyDTO,
|
||||||
|
|
@ -11,18 +11,26 @@ import {
|
||||||
UpdateGrafanaPluginSettingsProps,
|
UpdateGrafanaPluginSettingsProps,
|
||||||
} from './api.types';
|
} from './api.types';
|
||||||
|
|
||||||
|
const pluginId = getPluginId();
|
||||||
|
const KEY_NAME = {
|
||||||
|
[PluginId.OnCall]: 'OnCall',
|
||||||
|
[PluginId.Irm]: 'IRM',
|
||||||
|
}[pluginId];
|
||||||
|
const SERVICE_ACCOUNT_NAME = {
|
||||||
|
[PluginId.OnCall]: 'sa-autogen-OnCall',
|
||||||
|
[PluginId.Irm]: 'sa-autogen-IRM',
|
||||||
|
}[pluginId];
|
||||||
|
|
||||||
const KEYS_BASE_URL = '/api/auth/keys';
|
const KEYS_BASE_URL = '/api/auth/keys';
|
||||||
const SERVICE_ACCOUNTS_BASE_URL = '/api/serviceaccounts';
|
const SERVICE_ACCOUNTS_BASE_URL = '/api/serviceaccounts';
|
||||||
const ONCALL_KEY_NAME = 'OnCall';
|
const GRAFANA_PLUGIN_SETTINGS_URL = `/api/plugins/${pluginId}/settings`;
|
||||||
const ONCALL_SERVICE_ACCOUNT_NAME = 'sa-autogen-OnCall';
|
|
||||||
const GRAFANA_PLUGIN_SETTINGS_URL = `/api/plugins/${getPluginId()}/settings`;
|
|
||||||
|
|
||||||
export class GrafanaApiClient {
|
export class GrafanaApiClient {
|
||||||
static grafanaBackend = getBackendSrv();
|
static grafanaBackend = getBackendSrv();
|
||||||
|
|
||||||
private static getServiceAccount = async () => {
|
private static getServiceAccount = async () => {
|
||||||
const serviceAccounts = await this.grafanaBackend.get<PaginatedServiceAccounts>(
|
const serviceAccounts = await this.grafanaBackend.get<PaginatedServiceAccounts>(
|
||||||
`${SERVICE_ACCOUNTS_BASE_URL}/search?query=${ONCALL_SERVICE_ACCOUNT_NAME}`
|
`${SERVICE_ACCOUNTS_BASE_URL}/search?query=${SERVICE_ACCOUNT_NAME}`
|
||||||
);
|
);
|
||||||
return serviceAccounts.serviceAccounts.length > 0 ? serviceAccounts.serviceAccounts[0] : null;
|
return serviceAccounts.serviceAccounts.length > 0 ? serviceAccounts.serviceAccounts[0] : null;
|
||||||
};
|
};
|
||||||
|
|
@ -34,7 +42,7 @@ export class GrafanaApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.grafanaBackend.post<ServiceAccountDTO>(SERVICE_ACCOUNTS_BASE_URL, {
|
return await this.grafanaBackend.post<ServiceAccountDTO>(SERVICE_ACCOUNTS_BASE_URL, {
|
||||||
name: ONCALL_SERVICE_ACCOUNT_NAME,
|
name: SERVICE_ACCOUNT_NAME,
|
||||||
role: 'Admin',
|
role: 'Admin',
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
});
|
});
|
||||||
|
|
@ -44,7 +52,7 @@ export class GrafanaApiClient {
|
||||||
const tokens = await this.grafanaBackend.get<TokenDTO[]>(
|
const tokens = await this.grafanaBackend.get<TokenDTO[]>(
|
||||||
`${SERVICE_ACCOUNTS_BASE_URL}/${serviceAccount.id}/tokens`
|
`${SERVICE_ACCOUNTS_BASE_URL}/${serviceAccount.id}/tokens`
|
||||||
);
|
);
|
||||||
return tokens.find(({ name }) => name === ONCALL_KEY_NAME);
|
return tokens.find(({ name }) => name === KEY_NAME);
|
||||||
};
|
};
|
||||||
|
|
||||||
private static getGrafanaToken = async () => {
|
private static getGrafanaToken = async () => {
|
||||||
|
|
@ -54,7 +62,7 @@ export class GrafanaApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
const keys = await this.grafanaBackend.get<ApiAuthKeyDTO[]>(KEYS_BASE_URL);
|
const keys = await this.grafanaBackend.get<ApiAuthKeyDTO[]>(KEYS_BASE_URL);
|
||||||
return keys.find(({ name }) => name === ONCALL_KEY_NAME);
|
return keys.find(({ name }) => name === KEY_NAME);
|
||||||
};
|
};
|
||||||
|
|
||||||
static updateGrafanaPluginSettings = async (data: UpdateGrafanaPluginSettingsProps, enabled = true) =>
|
static updateGrafanaPluginSettings = async (data: UpdateGrafanaPluginSettingsProps, enabled = true) =>
|
||||||
|
|
@ -79,7 +87,7 @@ export class GrafanaApiClient {
|
||||||
const { key: grafanaToken } = await this.grafanaBackend.post<NewApiKeyResult>(
|
const { key: grafanaToken } = await this.grafanaBackend.post<NewApiKeyResult>(
|
||||||
`${SERVICE_ACCOUNTS_BASE_URL}/${serviceAccount.id}/tokens`,
|
`${SERVICE_ACCOUNTS_BASE_URL}/${serviceAccount.id}/tokens`,
|
||||||
{
|
{
|
||||||
name: ONCALL_KEY_NAME,
|
name: KEY_NAME,
|
||||||
role: 'Admin',
|
role: 'Admin',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue