The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/cloudmigration/api/api.go

405 lines
13 KiB

package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/services/cloudmigration"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/web"
)
type CloudMigrationAPI struct {
cloudMigrationService cloudmigration.Service
routeRegister routing.RouteRegister
log log.Logger
tracer tracing.Tracer
}
func RegisterApi(
rr routing.RouteRegister,
cms cloudmigration.Service,
tracer tracing.Tracer,
) *CloudMigrationAPI {
api := &CloudMigrationAPI{
log: log.New("cloudmigrations.api"),
routeRegister: rr,
cloudMigrationService: cms,
tracer: tracer,
}
api.registerEndpoints()
return api
}
// RegisterAPIEndpoints Registers Endpoints on Grafana Router
func (cma *CloudMigrationAPI) registerEndpoints() {
cma.routeRegister.Group("/api/cloudmigration", func(cloudMigrationRoute routing.RouteRegister) {
// migration
cloudMigrationRoute.Get("/migration", routing.Wrap(cma.GetMigrationList))
cloudMigrationRoute.Post("/migration", routing.Wrap(cma.CreateMigration))
cloudMigrationRoute.Get("/migration/:id", routing.Wrap(cma.GetMigration))
cloudMigrationRoute.Delete("/migration/:id", routing.Wrap(cma.DeleteMigration))
cloudMigrationRoute.Post("/migration/:id/run", routing.Wrap(cma.RunMigration))
cloudMigrationRoute.Get("/migration/:id/run", routing.Wrap(cma.GetMigrationRunList))
cloudMigrationRoute.Get("/migration/:id/run/:runID", routing.Wrap(cma.GetMigrationRun))
cloudMigrationRoute.Post("/token", routing.Wrap(cma.CreateToken))
}, middleware.ReqOrgAdmin)
}
// swagger:route POST /cloudmigration/token migrations createCloudMigrationToken
//
// Create gcom access token.
//
// Responses:
// 200: cloudMigrationCreateTokenResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (cma *CloudMigrationAPI) CreateToken(c *contextmodel.ReqContext) response.Response {
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.CreateAccessToken")
defer span.End()
logger := cma.log.FromContext(ctx)
resp, err := cma.cloudMigrationService.CreateToken(ctx)
if err != nil {
logger.Error("creating gcom access token", "err", err.Error())
return response.Error(http.StatusInternalServerError, "creating gcom access token", err)
}
return response.JSON(http.StatusOK, cloudmigration.CreateAccessTokenResponseDTO(resp))
}
// swagger:route GET /cloudmigration/migration migrations getMigrationList
//
// Get a list of all cloud migrations.
//
// Responses:
// 200: cloudMigrationListResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (cma *CloudMigrationAPI) GetMigrationList(c *contextmodel.ReqContext) response.Response {
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.GetMigrationList")
defer span.End()
cloudMigrations, err := cma.cloudMigrationService.GetMigrationList(ctx)
if err != nil {
return response.Error(http.StatusInternalServerError, "migration list error", err)
}
return response.JSON(http.StatusOK, cloudMigrations)
}
// swagger:route GET /cloudmigration/migration/{id} migrations getCloudMigration
//
// Get a cloud migration.
//
// It returns migrations that has been created.
//
// Responses:
// 200: cloudMigrationResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (cma *CloudMigrationAPI) GetMigration(c *contextmodel.ReqContext) response.Response {
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.GetMigration")
defer span.End()
id, err := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "id is invalid", err)
}
cloudMigration, err := cma.cloudMigrationService.GetMigration(ctx, id)
if err != nil {
return response.Error(http.StatusNotFound, "migration not found", err)
}
return response.JSON(http.StatusOK, cloudMigration)
}
// swagger:parameters getCloudMigration
type GetCloudMigrationRequest struct {
// ID of an migration
//
// in: path
ID int64 `json:"id"`
}
// swagger:route POST /cloudmigration/migration migrations createMigration
//
// Create a migration.
//
// Responses:
// 200: cloudMigrationResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (cma *CloudMigrationAPI) CreateMigration(c *contextmodel.ReqContext) response.Response {
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.CreateMigration")
defer span.End()
cmd := cloudmigration.CloudMigrationRequest{}
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
cloudMigration, err := cma.cloudMigrationService.CreateMigration(ctx, cmd)
if err != nil {
return response.Error(http.StatusInternalServerError, "migration creation error", err)
}
return response.JSON(http.StatusOK, cloudMigration)
}
// swagger:route POST /cloudmigration/migration/{id}/run migrations runCloudMigration
//
// Trigger the run of a migration to the Grafana Cloud.
//
// It returns migrations that has been created.
//
// Responses:
// 200: cloudMigrationRunResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (cma *CloudMigrationAPI) RunMigration(c *contextmodel.ReqContext) response.Response {
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.RunMigration")
defer span.End()
logger := cma.log.FromContext(ctx)
stringID := web.Params(c.Req)[":id"]
id, err := strconv.ParseInt(stringID, 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "id is invalid", err)
}
// Get migration to read the auth token
migration, err := cma.cloudMigrationService.GetMigration(ctx, id)
if err != nil {
return response.Error(http.StatusInternalServerError, "migration get error", err)
}
// get CMS path from the config
domain, err := cma.cloudMigrationService.ParseCloudMigrationConfig()
if err != nil {
return response.Error(http.StatusInternalServerError, "config parse error", err)
}
path := fmt.Sprintf("https://cms-%s.%s/cloud-migrations/api/v1/migrate-data", migration.ClusterSlug, domain)
// Get migration data JSON
body, err := cma.cloudMigrationService.GetMigrationDataJSON(ctx, id)
if err != nil {
cma.log.Error("error getting the json request body for migration run", "err", err.Error())
return response.Error(http.StatusInternalServerError, "migration data get error", err)
}
req, err := http.NewRequest(http.MethodPost, path, bytes.NewReader(body))
if err != nil {
cma.log.Error("error creating http request for cloud migration run", "err", err.Error())
return response.Error(http.StatusInternalServerError, "http request error", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %d:%s", migration.StackID, migration.AuthToken))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
cma.log.Error("error sending http request for cloud migration run", "err", err.Error())
return response.Error(http.StatusInternalServerError, "http request error", err)
} else if resp.StatusCode >= 400 {
cma.log.Error("received error response for cloud migration run", "statusCode", resp.StatusCode)
return response.Error(http.StatusInternalServerError, "http request error", fmt.Errorf("http request error while migrating data"))
}
defer func() {
if err := resp.Body.Close(); err != nil {
logger.Error("closing request body: %w", err)
}
}()
// read response so we can unmarshal it
respData, err := io.ReadAll(resp.Body)
if err != nil {
logger.Error("reading response body: %w", err)
return response.Error(http.StatusInternalServerError, "reading migration run response", err)
}
var result cloudmigration.MigrateDataResponseDTO
if err := json.Unmarshal(respData, &result); err != nil {
logger.Error("unmarshalling response body: %w", err)
return response.Error(http.StatusInternalServerError, "unmarshalling migration run response", err)
}
runID, err := cma.cloudMigrationService.SaveMigrationRun(ctx, &cloudmigration.CloudMigrationRun{
CloudMigrationUID: stringID,
Result: respData,
})
if err != nil {
response.Error(http.StatusInternalServerError, "migration run save error", err)
}
result.RunID = runID
return response.JSON(http.StatusOK, result)
}
// swagger:parameters runCloudMigration
type RunCloudMigrationRequest struct {
// ID of an migration
//
// in: path
ID int64 `json:"id"`
}
// swagger:route GET /cloudmigration/migration/{id}/run/{runID} migrations getCloudMigrationRun
//
// Get the result of a single migration run.
//
// Responses:
// 200: cloudMigrationRunResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (cma *CloudMigrationAPI) GetMigrationRun(c *contextmodel.ReqContext) response.Response {
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.GetMigrationRun")
defer span.End()
migrationStatus, err := cma.cloudMigrationService.GetMigrationStatus(ctx, web.Params(c.Req)[":id"], web.Params(c.Req)[":runID"])
if err != nil {
return response.Error(http.StatusInternalServerError, "migration status error", err)
}
runResponse, err := migrationStatus.ToResponse()
if err != nil {
cma.log.Error("could not return migration run", "err", err)
return response.Error(http.StatusInternalServerError, "migration run get error", err)
}
return response.JSON(http.StatusOK, runResponse)
}
// swagger:parameters getCloudMigrationRun
type GetMigrationRunParams struct {
// ID of an migration
//
// in: path
ID int64 `json:"id"`
// Run ID of a migration run
//
// in: path
RunID int64 `json:"runID"`
}
// swagger:route GET /cloudmigration/migration/{id}/run migrations getCloudMigrationRunList
//
// Get a list of migration runs for a migration.
//
// Responses:
// 200: cloudMigrationRunListResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (cma *CloudMigrationAPI) GetMigrationRunList(c *contextmodel.ReqContext) response.Response {
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.GetMigrationRunList")
defer span.End()
migrationStatuses, err := cma.cloudMigrationService.GetMigrationStatusList(ctx, web.Params(c.Req)[":id"])
if err != nil {
return response.Error(http.StatusInternalServerError, "migration status error", err)
}
runList := cloudmigration.CloudMigrationRunList{Runs: []cloudmigration.MigrateDataResponseDTO{}}
for _, s := range migrationStatuses {
// attempt to bind the raw result to a list of response item DTOs
r := cloudmigration.MigrateDataResponseDTO{
Items: []cloudmigration.MigrateDataResponseItemDTO{},
}
if err := json.Unmarshal(s.Result, &r); err != nil {
return response.Error(http.StatusInternalServerError, "error unmarshalling migration response items", err)
}
r.RunID = s.ID
runList.Runs = append(runList.Runs, r)
}
return response.JSON(http.StatusOK, runList)
}
// swagger:parameters getCloudMigrationRunList
type GetCloudMigrationRunList struct {
// ID of an migration
//
// in: path
ID int64 `json:"id"`
}
// swagger:route DELETE /cloudmigration/migration/{id} migrations deleteCloudMigration
//
// Delete a migration.
//
// Responses:
// 200
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (cma *CloudMigrationAPI) DeleteMigration(c *contextmodel.ReqContext) response.Response {
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.DeleteMigration")
defer span.End()
idStr := web.Params(c.Req)[":id"]
if idStr == "" {
return response.Error(http.StatusBadRequest, "missing migration id", fmt.Errorf("missing migration id"))
}
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "migration id should be numeric", fmt.Errorf("migration id should be numeric"))
}
_, err = cma.cloudMigrationService.DeleteMigration(ctx, id)
if err != nil {
return response.Error(http.StatusInternalServerError, "migration delete error", err)
}
return response.Empty(http.StatusOK)
}
// swagger:parameters deleteCloudMigration
type DeleteMigrationRequest struct {
// ID of an migration
//
// in: path
ID int64 `json:"id"`
}
// swagger:response cloudMigrationRunResponse
type CloudMigrationRunResponse struct {
// in: body
Body cloudmigration.MigrateDataResponseDTO
}
// swagger:response cloudMigrationListResponse
type CloudMigrationListResponse struct {
// in: body
Body cloudmigration.CloudMigrationListResponse
}
// swagger:response cloudMigrationResponse
type CloudMigrationResponse struct {
// in: body
Body cloudmigration.CloudMigrationResponse
}
// swagger:response cloudMigrationRunListResponse
type CloudMigrationRunListResponse struct {
// in: body
Body cloudmigration.CloudMigrationRunList
}
// swagger:response cloudMigrationCreateTokenResponse
type CloudMigrationCreateTokenResponse struct {
// in: body
Body cloudmigration.CreateAccessTokenResponseDTO
}