chronograf/cmd/chronoctl/migrate.go

246 lines
5.8 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"net/url"
"os"
"github.com/influxdata/chronograf"
"github.com/influxdata/chronograf/kv"
"github.com/influxdata/chronograf/kv/bolt"
"github.com/influxdata/chronograf/kv/etcd"
)
func init() {
parser.AddCommand("migrate",
"Migrate db (beta)",
"The migrate command (beta) will copy one db to another",
&migrateCommand{})
}
type migrateCommand struct {
From string `short:"f" long:"from" description:"Full path to boltDB file or etcd (e.g. 'bolt:///path/to/chronograf-v1.db' or 'etcd://user:pass@localhost:2379')" default:"chronograf-v1.db"`
To string `short:"t" long:"to" description:"Full path to boltDB file or etcd (e.g. 'bolt:///path/to/chronograf-v1.db' or 'etcds://user:pass@localhost:2379?cert=cert_path&key=key_path&ca=path_to_ca_certs')"`
}
func (m *migrateCommand) Execute(args []string) error {
if m.From == m.To {
errExit(errors.New("Cannot migrate to original source"))
}
if m.From == "" || m.To == "" {
errExit(errors.New("both 'to' and 'from' must be defined in order to migrate"))
}
ctx := context.TODO()
uFrom, err := url.Parse(m.From)
errExit(err)
uTo, err := url.Parse(m.To)
errExit(err)
datas, err := getData(ctx, uFrom)
errExit(err)
fmt.Printf("Performing non-idempotent db migration from %q to %q...\n", m.From, m.To)
fmt.Println("NOTICE: New IDs will be generated for each resource.")
errExit(saveData(ctx, uTo, datas))
fmt.Println("Migration successful!")
return nil
}
func openService(ctx context.Context, u *url.URL) (*kv.Service, error) {
var db kv.Store
var err error
switch u.Scheme {
case "bolt", "boltdb", "":
if u.Host != "" {
return nil, errors.New("ambiguous uri")
}
db, err = bolt.NewClient(ctx,
bolt.WithPath(u.Path),
)
if err != nil {
return nil, fmt.Errorf("unable to create bolt client: %s", err)
}
case "etcd", "etcds":
db, err = etcd.NewClient(ctx, etcd.WithURL(u))
if err != nil {
return nil, fmt.Errorf("unable to create etcd client: %s", err)
}
default:
return nil, fmt.Errorf("invalid uri scheme '%s'", u.Scheme)
}
return kv.NewService(ctx, db)
}
func getData(ctx context.Context, fromURL *url.URL) (*datas, error) {
switch fromURL.Scheme {
case "bolt", "boltdb", "":
_, err := os.Stat(fromURL.Path)
if os.IsNotExist(err) {
return nil, err
}
}
from, err := openService(ctx, fromURL)
if err != nil {
return nil, err
}
defer from.Close()
cfg, err := from.ConfigStore().Get(ctx)
if err != nil {
return nil, err
}
dashboards, err := from.DashboardsStore().All(ctx)
if err != nil {
return nil, err
}
mappings, err := from.MappingsStore().All(ctx)
if err != nil {
return nil, err
}
orgCfgs, err := from.OrganizationConfigStore().All(ctx)
if err != nil {
return nil, err
}
orgs, err := from.OrganizationsStore().All(ctx)
if err != nil {
return nil, err
}
servers, err := from.ServersStore().All(ctx)
if err != nil {
return nil, err
}
srcs, err := from.SourcesStore().All(ctx)
if err != nil {
return nil, err
}
users, err := from.UsersStore().All(ctx)
if err != nil {
return nil, err
}
return &datas{
config: *cfg,
dashboards: dashboards,
mappings: mappings,
orgConfigs: orgCfgs,
organizations: orgs,
servers: servers,
sources: srcs,
users: users,
}, nil
}
func saveData(ctx context.Context, toURL *url.URL, datas *datas) error {
to, err := openService(ctx, toURL)
if err != nil {
return fmt.Errorf("failed to open service '%s': %s", toURL.String(), err)
}
defer to.Close()
err = to.ConfigStore().Update(ctx, &datas.config)
if err != nil {
return err
}
for _, org := range datas.organizations {
_, err = to.OrganizationsStore().Add(ctx, &org)
if err != nil {
if err == chronograf.ErrOrganizationAlreadyExists {
err = to.OrganizationsStore().Update(ctx, &org)
if err == nil {
continue
}
}
return fmt.Errorf("failed to add to OrganizationsStore: %s", err)
}
}
fmt.Printf(" Saved %d organizations.\n", len(datas.organizations))
for _, orgCfg := range datas.orgConfigs {
err = to.OrganizationConfigStore().Put(ctx, &orgCfg)
if err != nil {
return fmt.Errorf("failed to add to OrganizationConfigStore: %s", err)
}
}
fmt.Printf(" Saved %d organization configs.\n", len(datas.orgConfigs))
for _, dash := range datas.dashboards {
_, err = to.DashboardsStore().Add(ctx, dash)
if err != nil {
return fmt.Errorf("failed to add to DashboardsStore: %s", err)
}
}
fmt.Printf(" Saved %d dashboards.\n", len(datas.dashboards))
for _, mapping := range datas.mappings {
_, err = to.MappingsStore().Add(ctx, &mapping)
if err != nil {
return fmt.Errorf("failed to add to MappingsStore: %s", err)
}
}
fmt.Printf(" Saved %d mappings.\n", len(datas.mappings))
for _, server := range datas.servers {
_, err = to.ServersStore().Add(ctx, server)
if err != nil {
return fmt.Errorf("failed to add to ServersStore: %s", err)
}
}
fmt.Printf(" Saved %d servers.\n", len(datas.servers))
for _, source := range datas.sources {
_, err = to.SourcesStore().Add(ctx, source)
if err != nil {
return fmt.Errorf("failed to add to SourcesStore: %s", err)
}
}
fmt.Printf(" Saved %d sources.\n", len(datas.sources))
for _, user := range datas.users {
_, err = to.UsersStore().Add(ctx, &user)
if err != nil {
return fmt.Errorf("failed to add to UsersStore: %s", err)
}
}
fmt.Printf(" Saved %d users.\n", len(datas.users))
return nil
}
type datas struct {
config chronograf.Config
dashboards []chronograf.Dashboard
mappings []chronograf.Mapping
orgConfigs []chronograf.OrganizationConfig
organizations []chronograf.Organization
servers []chronograf.Server
sources []chronograf.Source
users []chronograf.User
}
func errExit(err error) {
if err == nil {
return
}
fmt.Println(err.Error())
os.Exit(1)
}