2024-02-26 21:52:16 +08:00
|
|
|
package cloudmigrationimpl
|
2024-01-23 00:09:08 +08:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-03-25 23:43:28 +08:00
|
|
|
"encoding/base64"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
2024-01-23 00:09:08 +08:00
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/api/routing"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2024-03-25 23:43:28 +08:00
|
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
2024-02-26 21:52:16 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/cloudmigration"
|
|
|
|
"github.com/grafana/grafana/pkg/services/cloudmigration/api"
|
2024-01-23 00:09:08 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/datasources"
|
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
2024-03-25 23:43:28 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/gcom"
|
2024-01-23 00:09:08 +08:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
)
|
|
|
|
|
|
|
|
// CloudMigrationsServiceImpl Define the Service Implementation.
|
2024-02-26 21:52:16 +08:00
|
|
|
type Service struct {
|
|
|
|
store store
|
|
|
|
|
2024-03-25 23:43:28 +08:00
|
|
|
log *log.ConcreteLogger
|
2024-01-23 00:09:08 +08:00
|
|
|
cfg *setting.Cfg
|
|
|
|
|
2024-03-25 23:43:28 +08:00
|
|
|
features featuremgmt.FeatureToggles
|
|
|
|
dsService datasources.DataSourceService
|
|
|
|
gcomService gcom.Service
|
2024-01-23 00:09:08 +08:00
|
|
|
|
2024-03-25 23:43:28 +08:00
|
|
|
api *api.CloudMigrationAPI
|
|
|
|
tracer tracing.Tracer
|
|
|
|
metrics *Metrics
|
2024-01-23 00:09:08 +08:00
|
|
|
}
|
|
|
|
|
2024-02-26 21:52:16 +08:00
|
|
|
var LogPrefix = "cloudmigration.service"
|
2024-01-23 00:09:08 +08:00
|
|
|
|
2024-03-25 23:43:28 +08:00
|
|
|
const (
|
|
|
|
// nolint:gosec
|
|
|
|
cloudMigrationAccessPolicyName = "grafana-cloud-migrations"
|
|
|
|
//nolint:gosec
|
|
|
|
cloudMigrationTokenName = "grafana-cloud-migrations"
|
|
|
|
)
|
|
|
|
|
2024-02-26 21:52:16 +08:00
|
|
|
var _ cloudmigration.Service = (*Service)(nil)
|
2024-01-23 00:09:08 +08:00
|
|
|
|
|
|
|
// ProvideService Factory for method used by wire to inject dependencies.
|
|
|
|
// builds the service, and api, and configures routes
|
|
|
|
func ProvideService(
|
|
|
|
cfg *setting.Cfg,
|
|
|
|
features featuremgmt.FeatureToggles,
|
2024-02-26 21:52:16 +08:00
|
|
|
db db.DB,
|
2024-01-23 00:09:08 +08:00
|
|
|
dsService datasources.DataSourceService,
|
|
|
|
routeRegister routing.RouteRegister,
|
|
|
|
prom prometheus.Registerer,
|
2024-03-25 23:43:28 +08:00
|
|
|
tracer tracing.Tracer,
|
2024-02-26 21:52:16 +08:00
|
|
|
) cloudmigration.Service {
|
2024-01-23 00:09:08 +08:00
|
|
|
if !features.IsEnabledGlobally(featuremgmt.FlagOnPremToCloudMigrations) {
|
|
|
|
return &NoopServiceImpl{}
|
|
|
|
}
|
|
|
|
|
2024-02-26 21:52:16 +08:00
|
|
|
s := &Service{
|
2024-03-25 23:43:28 +08:00
|
|
|
store: &sqlStore{db: db},
|
|
|
|
log: log.New(LogPrefix),
|
|
|
|
cfg: cfg,
|
|
|
|
features: features,
|
|
|
|
dsService: dsService,
|
|
|
|
gcomService: gcom.New(gcom.Config{ApiURL: cfg.GrafanaComAPIURL, Token: cfg.CloudMigration.GcomAPIToken}),
|
|
|
|
tracer: tracer,
|
|
|
|
metrics: newMetrics(),
|
2024-01-23 00:09:08 +08:00
|
|
|
}
|
2024-03-25 23:43:28 +08:00
|
|
|
s.api = api.RegisterApi(routeRegister, s, tracer)
|
2024-01-23 00:09:08 +08:00
|
|
|
|
2024-03-25 23:43:28 +08:00
|
|
|
if err := s.registerMetrics(prom, s.metrics); err != nil {
|
2024-01-23 00:09:08 +08:00
|
|
|
s.log.Warn("error registering prom metrics", "error", err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2024-03-25 23:43:28 +08:00
|
|
|
func (s *Service) CreateToken(ctx context.Context) (cloudmigration.CreateAccessTokenResponse, error) {
|
|
|
|
ctx, span := s.tracer.Start(ctx, "CloudMigrationService.CreateToken")
|
|
|
|
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 cloudmigration.CreateAccessTokenResponse{}, fmt.Errorf("fetching instance by id: id=%s %w", s.cfg.StackID, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
timeoutCtx, cancel = context.WithTimeout(ctx, s.cfg.CloudMigration.FetchAccessPolicyTimeout)
|
|
|
|
defer cancel()
|
|
|
|
existingAccessPolicy, err := s.findAccessPolicyByName(timeoutCtx, instance.RegionSlug, cloudMigrationAccessPolicyName)
|
|
|
|
if err != nil {
|
|
|
|
return cloudmigration.CreateAccessTokenResponse{}, fmt.Errorf("fetching access policy by name: name=%s %w", cloudMigrationAccessPolicyName, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if existingAccessPolicy != nil {
|
|
|
|
timeoutCtx, cancel := context.WithTimeout(ctx, s.cfg.CloudMigration.DeleteAccessPolicyTimeout)
|
|
|
|
defer cancel()
|
|
|
|
if _, err := s.gcomService.DeleteAccessPolicy(timeoutCtx, gcom.DeleteAccessPolicyParams{
|
|
|
|
RequestID: requestID,
|
|
|
|
AccessPolicyID: existingAccessPolicy.ID,
|
|
|
|
Region: instance.RegionSlug,
|
|
|
|
}); err != nil {
|
|
|
|
return cloudmigration.CreateAccessTokenResponse{}, fmt.Errorf("deleting access policy: id=%s region=%s %w", existingAccessPolicy.ID, instance.RegionSlug, err)
|
|
|
|
}
|
|
|
|
logger.Info("deleted access policy", existingAccessPolicy.ID, "name", existingAccessPolicy.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
timeoutCtx, cancel = context.WithTimeout(ctx, s.cfg.CloudMigration.CreateAccessPolicyTimeout)
|
|
|
|
defer cancel()
|
|
|
|
accessPolicy, err := s.gcomService.CreateAccessPolicy(timeoutCtx,
|
|
|
|
gcom.CreateAccessPolicyParams{
|
|
|
|
RequestID: requestID,
|
|
|
|
Region: instance.RegionSlug,
|
|
|
|
},
|
|
|
|
gcom.CreateAccessPolicyPayload{
|
|
|
|
Name: cloudMigrationAccessPolicyName,
|
|
|
|
DisplayName: cloudMigrationAccessPolicyName,
|
|
|
|
Realms: []gcom.Realm{{Type: "stack", Identifier: s.cfg.StackID, LabelPolicies: []gcom.LabelPolicy{}}},
|
|
|
|
Scopes: []string{"cloud-migrations:read", "cloud-migrations:write"},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return cloudmigration.CreateAccessTokenResponse{}, fmt.Errorf("creating access policy: %w", err)
|
|
|
|
}
|
|
|
|
logger.Info("created access policy", "id", accessPolicy.ID, "name", accessPolicy.Name)
|
|
|
|
|
|
|
|
timeoutCtx, cancel = context.WithTimeout(ctx, s.cfg.CloudMigration.CreateTokenTimeout)
|
|
|
|
defer cancel()
|
|
|
|
token, err := s.gcomService.CreateToken(timeoutCtx,
|
|
|
|
gcom.CreateTokenParams{RequestID: requestID, Region: instance.RegionSlug},
|
|
|
|
gcom.CreateTokenPayload{
|
|
|
|
AccessPolicyID: accessPolicy.ID,
|
|
|
|
DisplayName: cloudMigrationTokenName,
|
|
|
|
Name: cloudMigrationTokenName,
|
|
|
|
ExpiresAt: time.Now().Add(s.cfg.CloudMigration.TokenExpiresAfter),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return cloudmigration.CreateAccessTokenResponse{}, fmt.Errorf("creating access token: %w", err)
|
|
|
|
}
|
|
|
|
logger.Info("created access token", "id", token.ID, "name", token.Name)
|
|
|
|
s.metrics.accessTokenCreated.With(prometheus.Labels{"slug": s.cfg.Slug}).Inc()
|
|
|
|
|
|
|
|
bytes, err := json.Marshal(map[string]string{
|
|
|
|
"token": token.Token,
|
|
|
|
"region": instance.ClusterSlug,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return cloudmigration.CreateAccessTokenResponse{}, fmt.Errorf("encoding token: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return cloudmigration.CreateAccessTokenResponse{Token: base64.StdEncoding.EncodeToString(bytes)}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) findAccessPolicyByName(ctx context.Context, regionSlug, accessPolicyName string) (*gcom.AccessPolicy, error) {
|
|
|
|
ctx, span := s.tracer.Start(ctx, "CloudMigrationService.findAccessPolicyByName")
|
|
|
|
defer span.End()
|
|
|
|
|
|
|
|
accessPolicies, err := s.gcomService.ListAccessPolicies(ctx, gcom.ListAccessPoliciesParams{
|
|
|
|
RequestID: tracing.TraceIDFromContext(ctx, false),
|
|
|
|
Region: regionSlug,
|
|
|
|
Name: accessPolicyName,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("listing access policies: name=%s region=%s :%w", accessPolicyName, regionSlug, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, accessPolicy := range accessPolicies {
|
|
|
|
if accessPolicy.Name == accessPolicyName {
|
|
|
|
return &accessPolicy, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
2024-01-23 00:09:08 +08:00
|
|
|
}
|
2024-03-25 20:30:47 +08:00
|
|
|
|
|
|
|
func (s *Service) ValidateToken(ctx context.Context, token string) error {
|
|
|
|
// TODO: Implement method
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) SaveEncryptedToken(ctx context.Context, token string) error {
|
|
|
|
// TODO: Implement method
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) GetMigration(ctx context.Context, id int64) (*cloudmigration.CloudMigrationResponse, error) {
|
|
|
|
// TODO: Implement method
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) GetMigrationList(ctx context.Context) ([]cloudmigration.CloudMigrationResponse, error) {
|
|
|
|
// TODO: Implement method
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) CreateMigration(ctx context.Context, cm cloudmigration.CloudMigrationRequest) (*cloudmigration.CloudMigrationResponse, error) {
|
|
|
|
// TODO: Implement method
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) UpdateMigration(ctx context.Context, id int64, cm cloudmigration.CloudMigrationRequest) (*cloudmigration.CloudMigrationResponse, error) {
|
|
|
|
// TODO: Implement method
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) RunMigration(ctx context.Context, uid string) (*cloudmigration.CloudMigrationRun, error) {
|
|
|
|
// TODO: Implement method
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) GetMigrationStatus(ctx context.Context, id string, runID string) (*cloudmigration.CloudMigrationRun, error) {
|
|
|
|
// TODO: Implement method
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) GetMigrationStatusList(ctx context.Context, id string) ([]cloudmigration.CloudMigrationRun, error) {
|
|
|
|
// TODO: Implement method
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) DeleteMigration(ctx context.Context, id string) error {
|
|
|
|
// TODO: Implement method
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// func (s *Service) MigrateDatasources(ctx context.Context, request *cloudmigration.MigrateDatasourcesRequest) (*cloudmigration.MigrateDatasourcesResponse, error) {
|
|
|
|
// return s.store.MigrateDatasources(ctx, request)
|
|
|
|
// }
|