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:
Bruno
2024-05-31 09:39:10 -03:00
committed by GitHub
parent b640921dfd
commit 83543c6b12
10 changed files with 351 additions and 1 deletions
+63
View File
@@ -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
}
+1
View File
@@ -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