Cloud migrations: create route to fetch cloud migration token (#88176)
* Cloud migration: create route to fetch cloud migration token * implement gcomStub.ListTokens * fix swagger for POST /cloudmigration/migration * fix swagger for POST /cloudmigration/migration * fix swagger for POST /cloudmigration/migration
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
@@ -47,10 +48,46 @@ func (cma *CloudMigrationAPI) registerEndpoints() {
|
||||
cloudMigrationRoute.Post("/migration/:uid/run", routing.Wrap(cma.RunMigration))
|
||||
cloudMigrationRoute.Get("/migration/:uid/run", routing.Wrap(cma.GetMigrationRunList))
|
||||
cloudMigrationRoute.Get("/migration/run/:runUID", routing.Wrap(cma.GetMigrationRun))
|
||||
cloudMigrationRoute.Get("/token", routing.Wrap(cma.GetToken))
|
||||
cloudMigrationRoute.Post("/token", routing.Wrap(cma.CreateToken))
|
||||
}, middleware.ReqOrgAdmin)
|
||||
}
|
||||
|
||||
// swagger:route GET /cloudmigration/token migrations getCloudMigrationToken
|
||||
//
|
||||
// Fetch the cloud migration token if it exists.
|
||||
//
|
||||
// Responses:
|
||||
// 200: cloudMigrationGetTokenResponse
|
||||
// 401: unauthorisedError
|
||||
// 404: notFoundError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (cma *CloudMigrationAPI) GetToken(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.GetToken")
|
||||
defer span.End()
|
||||
|
||||
logger := cma.log.FromContext(ctx)
|
||||
|
||||
token, err := cma.cloudMigrationService.GetToken(ctx)
|
||||
if err != nil {
|
||||
if !errors.Is(err, cloudmigration.ErrTokenNotFound) {
|
||||
logger.Error("fetching cloud migration access token", "err", err.Error())
|
||||
}
|
||||
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "fetching cloud migration access token", err)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, GetAccessTokenResponseDTO{
|
||||
ID: token.ID,
|
||||
DisplayName: token.DisplayName,
|
||||
ExpiresAt: token.ExpiresAt,
|
||||
FirstUsedAt: token.FirstUsedAt,
|
||||
LastUsedAt: token.LastUsedAt,
|
||||
CreatedAt: token.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:route POST /cloudmigration/token migrations createCloudMigrationToken
|
||||
//
|
||||
// Create gcom access token.
|
||||
@@ -310,6 +347,13 @@ type CloudMigrationListResponse struct {
|
||||
Body cloudmigration.CloudMigrationListResponse
|
||||
}
|
||||
|
||||
// swagger:parameters createMigration
|
||||
type CreateMigration struct {
|
||||
// in:body
|
||||
// required:true
|
||||
Body cloudmigration.CloudMigrationRequest
|
||||
}
|
||||
|
||||
// swagger:response cloudMigrationResponse
|
||||
type CloudMigrationResponse struct {
|
||||
// in: body
|
||||
@@ -322,6 +366,25 @@ type CloudMigrationRunListResponse struct {
|
||||
Body cloudmigration.CloudMigrationRunList
|
||||
}
|
||||
|
||||
// swagger:parameters getCloudMigrationToken
|
||||
type GetCloudMigrationToken struct {
|
||||
}
|
||||
|
||||
// swagger:response cloudMigrationGetTokenResponse
|
||||
type CloudMigrationGetTokenResponse struct {
|
||||
// in: body
|
||||
Body GetAccessTokenResponseDTO
|
||||
}
|
||||
|
||||
type GetAccessTokenResponseDTO struct {
|
||||
ID string `json:"id"`
|
||||
DisplayName string `json:"displayName"`
|
||||
ExpiresAt string `json:"expiresAt"`
|
||||
FirstUsedAt string `json:"firstUsedAt"`
|
||||
LastUsedAt string `json:"lastUsedAt"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
// swagger:response cloudMigrationCreateTokenResponse
|
||||
type CloudMigrationCreateTokenResponse struct {
|
||||
// in: body
|
||||
|
||||
@@ -2,10 +2,16 @@ package cloudmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/gcom"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
// Returns the cloud migration token if it exists.
|
||||
GetToken(context.Context) (gcom.TokenView, error)
|
||||
// Creates a cloud migration token.
|
||||
CreateToken(context.Context) (CreateAccessTokenResponse, error)
|
||||
// Sends a request to CMS to test the token.
|
||||
ValidateToken(context.Context, CloudMigration) error
|
||||
|
||||
CreateMigration(context.Context, CloudMigrationRequest) (*CloudMigrationResponse, error)
|
||||
|
||||
@@ -102,7 +102,7 @@ func ProvideService(
|
||||
s.gcomService = gcom.New(gcom.Config{ApiURL: cfg.GrafanaComAPIURL, Token: cfg.CloudMigration.GcomAPIToken})
|
||||
} else {
|
||||
s.cmsClient = cmsclient.NewInMemoryClient()
|
||||
s.gcomService = &gcomStub{map[string]gcom.AccessPolicy{}}
|
||||
s.gcomService = &gcomStub{policies: map[string]gcom.AccessPolicy{}, token: nil}
|
||||
s.cfg.StackID = "12345"
|
||||
}
|
||||
|
||||
@@ -113,6 +113,47 @@ func ProvideService(
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetToken(ctx context.Context) (gcom.TokenView, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "CloudMigrationService.GetToken")
|
||||
defer span.End()
|
||||
logger := s.log.FromContext(ctx)
|
||||
requestID := tracing.TraceIDFromContext(ctx, false)
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, s.cfg.CloudMigration.FetchInstanceTimeout)
|
||||
defer cancel()
|
||||
instance, err := s.gcomService.GetInstanceByID(timeoutCtx, requestID, s.cfg.StackID)
|
||||
if err != nil {
|
||||
return gcom.TokenView{}, fmt.Errorf("fetching instance by id: id=%s %w", s.cfg.StackID, err)
|
||||
}
|
||||
|
||||
logger.Info("instance found", "slug", instance.Slug)
|
||||
|
||||
accessPolicyName := fmt.Sprintf("%s-%s", cloudMigrationAccessPolicyNamePrefix, s.cfg.StackID)
|
||||
accessTokenName := fmt.Sprintf("%s-%s", cloudMigrationTokenNamePrefix, s.cfg.StackID)
|
||||
|
||||
timeoutCtx, cancel = context.WithTimeout(ctx, s.cfg.CloudMigration.ListTokensTimeout)
|
||||
defer cancel()
|
||||
tokens, err := s.gcomService.ListTokens(timeoutCtx, gcom.ListTokenParams{
|
||||
RequestID: requestID,
|
||||
Region: instance.RegionSlug,
|
||||
AccessPolicyName: accessPolicyName,
|
||||
TokenName: accessTokenName})
|
||||
if err != nil {
|
||||
return gcom.TokenView{}, fmt.Errorf("listing tokens: %w", err)
|
||||
}
|
||||
logger.Info("found access tokens", "num_tokens", len(tokens))
|
||||
|
||||
for _, token := range tokens {
|
||||
if token.Name == accessTokenName {
|
||||
logger.Info("found existing cloud migration token", "tokenID", token.ID, "accessPolicyID", token.AccessPolicyID)
|
||||
return token, nil
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info("cloud migration token not found")
|
||||
return gcom.TokenView{}, cloudmigration.ErrTokenNotFound
|
||||
}
|
||||
|
||||
func (s *Service) CreateToken(ctx context.Context) (cloudmigration.CreateAccessTokenResponse, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "CloudMigrationService.CreateToken")
|
||||
defer span.End()
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/cloudmigration"
|
||||
"github.com/grafana/grafana/pkg/services/gcom"
|
||||
)
|
||||
|
||||
// NoopServiceImpl Define the Service Implementation.
|
||||
@@ -11,6 +12,10 @@ type NoopServiceImpl struct{}
|
||||
|
||||
var _ cloudmigration.Service = (*NoopServiceImpl)(nil)
|
||||
|
||||
func (s *NoopServiceImpl) GetToken(ctx context.Context) (gcom.TokenView, error) {
|
||||
return gcom.TokenView{}, cloudmigration.ErrFeatureDisabledError
|
||||
}
|
||||
|
||||
func (s *NoopServiceImpl) CreateToken(ctx context.Context) (cloudmigration.CreateAccessTokenResponse, error) {
|
||||
return cloudmigration.CreateAccessTokenResponse{}, cloudmigration.ErrFeatureDisabledError
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
type gcomStub struct {
|
||||
// The cloud migration token created by this stub.
|
||||
token *gcom.TokenView
|
||||
policies map[string]gcom.AccessPolicy
|
||||
}
|
||||
|
||||
@@ -49,6 +51,14 @@ func (client *gcomStub) ListAccessPolicies(ctx context.Context, params gcom.List
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (client *gcomStub) ListTokens(ctx context.Context, params gcom.ListTokenParams) ([]gcom.TokenView, error) {
|
||||
if client.token == nil {
|
||||
return []gcom.TokenView{}, nil
|
||||
}
|
||||
|
||||
return []gcom.TokenView{*client.token}, nil
|
||||
}
|
||||
|
||||
func (client *gcomStub) CreateToken(ctx context.Context, params gcom.CreateTokenParams, payload gcom.CreateTokenPayload) (gcom.Token, error) {
|
||||
token := gcom.Token{
|
||||
ID: fmt.Sprintf("random-token-%s", util.GenerateShortUID()),
|
||||
@@ -56,5 +66,11 @@ func (client *gcomStub) CreateToken(ctx context.Context, params gcom.CreateToken
|
||||
AccessPolicyID: payload.AccessPolicyID,
|
||||
Token: fmt.Sprintf("completely_fake_token_%s", util.GenerateShortUID()),
|
||||
}
|
||||
client.token = &gcom.TokenView{
|
||||
ID: token.ID,
|
||||
Name: token.Name,
|
||||
AccessPolicyID: token.AccessPolicyID,
|
||||
DisplayName: token.Name,
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ var (
|
||||
ErrMigrationNotFound = errutil.NotFound("cloudmigrations.migrationNotFound").Errorf("Migration not found")
|
||||
ErrMigrationRunNotFound = errutil.NotFound("cloudmigrations.migrationRunNotFound").Errorf("Migration run not found")
|
||||
ErrMigrationNotDeleted = errutil.Internal("cloudmigrations.migrationNotDeleted").Errorf("Migration not deleted")
|
||||
ErrTokenNotFound = errutil.NotFound("cloudmigrations.tokenNotFound").Errorf("Token not found")
|
||||
)
|
||||
|
||||
// CloudMigration api dtos
|
||||
|
||||
Reference in New Issue
Block a user