diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2b1923904..d4f0167e6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -33,7 +33,7 @@ jobs: uses: golangci/golangci-lint-action@v6 with: # The `only-new-issues` flag is not working (https://github.com/golangci/golangci-lint-action/issues/531). - # We rather decided to use the suggestion from the FAQ (https://golangci-lint.run/usage/faq/#how-to-integrate-golangci-lint-into-large-project-with-thousands-of-issues) and use `--new-from-rev` + # We rather decided to use the suggestion from the FAQ (https://golangci-lint.run/welcome/faq/#how-to-integrate-golangci-lint-into-large-project-with-thousands-of-issues) and use `--new-from-rev` # only-new-issues: false args: "--config=$(pwd)/.golangci.yml --new-from-rev=${{ steps.new-from-rev.outputs.NEW_FROM_REV }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aad329c8..b43c391a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### To be Released +* feat(regionmigrations): remove the commands + ### 1.34.0 * feat(database/users): raise minimum user password length to 24 ([PR#1077](https://github.com/Scalingo/cli/pull/1077)) diff --git a/README.md b/README.md index 5ebd21423..01501f712 100644 --- a/README.md +++ b/README.md @@ -65,23 +65,32 @@ COMMANDS: help Shows a list of commands or help for one command Addons: - addons List used add-ons - addons-add Provision an add-on for your application - addons-remove Remove an existing addon from your app - addons-upgrade Upgrade or downgrade an add-on attached to your app - addons-info Display information about an add-on attached to your app - backups-config Configure the periodic backups of a database - database-enable-feature Enable a togglable feature from a database - database-disable-feature Enable a togglable feature from a database - backups List backups for an addon - backups-create Ask for a new backup - backups-download Download a backup - backup-download Download a backup + addons List used add-ons + addons-add Provision an add-on for your application + addons-remove Remove an existing addon from your app + addons-upgrade Upgrade or downgrade an add-on attached to your app + addons-info Display information about an add-on attached to your app + addons-config Configure an add-on attached to your app + backups-config Configure the periodic backups of a database + database-enable-feature Enable a togglable feature from a database + database-disable-feature Enable a togglable feature from a database + database-users-list, database-list-users Print database's users + database-users-delete, database-delete-user Delete a database's user + database-users-create, database-create-user Create new database user + database-users-update-password, database-update-user-password Update a database user's password + database-maintenance-info Show a database maintenance + backups List backups for an addon + backups-create Ask for a new backup + backups-download Download a backup + backup-download Download a backup Addons - Global: addons-list List all addons addons-plans List plans + Addons Maintenance: + database-maintenance-list List the past and future maintenance on the given database + Alerts: alerts List the alerts of an application alerts-add Add an alert to an application metric @@ -211,13 +220,6 @@ COMMANDS: keys-add Add a public SSH key to deploy your apps keys-remove Remove a public SSH key - Region migrations: - migration-create Start migrating an app to another region - migration-run Run a specific migration step - migration-abort Abort a migration - migrations List all migrations linked to an app - migration-follow Follow a running migration - Review Apps: review-apps Show review apps of the parent application @@ -316,4 +318,4 @@ You can now update the [changelog](https://doc.scalingo.com/changelog) and tweet > [Changelog] CLI - Release of version 1.34.0 https://cli.scalingo.com - More news at https://changelog.scalingo.com #cli #paas #changelog #bugfix Add in a tweets thread the changelog of this new version. -1.34.0 \ No newline at end of file +1.34.0 diff --git a/cmd/autocomplete/region_migrations.go b/cmd/autocomplete/region_migrations.go deleted file mode 100644 index e8e5a4ec6..000000000 --- a/cmd/autocomplete/region_migrations.go +++ /dev/null @@ -1,33 +0,0 @@ -package autocomplete - -import ( - "fmt" - - "github.com/urfave/cli/v2" - errgo "gopkg.in/errgo.v1" - - "github.com/Scalingo/cli/config" -) - -func RegionMigrationsAutoComplete(c *cli.Context) error { - appName := CurrentAppCompletion(c) - if appName == "" { - return nil - } - - client, err := config.ScalingoClient(c.Context) - if err != nil { - return errgo.Notef(err, "fail to get Scalingo client") - } - - migrations, err := client.ListRegionMigrations(c.Context, appName) - if err != nil { - return nil - } - - for _, migration := range migrations { - fmt.Println(migration.ID) - } - - return nil -} diff --git a/cmd/commands.go b/cmd/commands.go index 1e5983e34..3b02dca94 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -281,13 +281,6 @@ var ( &autoscalersDisableCommand, &autoscalersEnableCommand, - // Migrations - &migrationCreateCommand, - &migrationRunCommand, - &migrationAbortCommand, - &migrationListCommand, - &migrationFollowCommand, - // Log drains &logDrainsAddCommand, &logDrainsListCommand, diff --git a/cmd/region_migrations.go b/cmd/region_migrations.go deleted file mode 100644 index 7f982aa9e..000000000 --- a/cmd/region_migrations.go +++ /dev/null @@ -1,177 +0,0 @@ -package cmd - -import ( - "github.com/urfave/cli/v2" - - "github.com/Scalingo/cli/cmd/autocomplete" - "github.com/Scalingo/cli/detect" - "github.com/Scalingo/cli/regionmigrations" - "github.com/Scalingo/cli/utils" - "github.com/Scalingo/go-scalingo/v7" -) - -var ( - migrationCreateCommand = cli.Command{ - Name: "migration-create", - Category: "Region migrations", - Flags: []cli.Flag{ - &appFlag, - &cli.StringFlag{Name: "to", Usage: "Select the destination region"}, - &cli.StringFlag{Name: "new-name", Usage: "Name of the app in the destination region (same as origin by default)"}, - }, - Usage: "Start migrating an app to another region", - Description: CommandDescription{ - Description: "Migrate an app to another region", - Examples: []string{"scalingo --app my-app migration-create --to osc-fr1"}, - }.Render(), - Action: func(c *cli.Context) error { - currentApp := detect.CurrentApp(c) - if c.Args().Len() != 0 { - cli.ShowCommandHelp(c, "migration-create") - return nil - } - - if c.String("to") == "" { - cli.ShowCommandHelp(c, "migration-create") - return nil - } - - utils.CheckForConsent(c.Context, currentApp) - - err := regionmigrations.Create(c.Context, currentApp, c.String("to"), c.String("new-name")) - if err != nil { - errorQuit(c.Context, err) - } - return nil - }, - } - migrationRunCommand = cli.Command{ - Name: "migration-run", - Category: "Region migrations", - Flags: []cli.Flag{ - &appFlag, - &cli.BoolFlag{Name: "prepare", Usage: "Create an empty canvas on the new region"}, - &cli.BoolFlag{Name: "data", Usage: "Import databases (and their data) to the new region"}, - &cli.BoolFlag{Name: "finalize", Usage: "Stop the old app and start the new one"}, - }, - Usage: "Run a specific migration step", - Description: CommandDescription{ - Description: "Run a migration step", - Examples: []string{"scalingo --app my-app migration-run --prepare migration-id"}, - }.Render(), - - Action: func(c *cli.Context) error { - if c.Args().Len() != 1 { - cli.ShowCommandHelp(c, "migration-run") - return nil - } - var step scalingo.RegionMigrationStep - migrationID := c.Args().First() - currentApp := detect.CurrentApp(c) - - utils.CheckForConsent(c.Context, currentApp) - - stepsFound := 0 - if c.Bool("prepare") { - stepsFound++ - step = scalingo.RegionMigrationStepPrepare - } - if c.Bool("data") { - stepsFound++ - step = scalingo.RegionMigrationStepData - } - if c.Bool("finalize") { - stepsFound++ - step = scalingo.RegionMigrationStepFinalize - } - if stepsFound != 1 { - cli.ShowCommandHelp(c, "migration-run") - return nil - } - - err := regionmigrations.Run(c.Context, currentApp, migrationID, step) - if err != nil { - errorQuit(c.Context, err) - } - return nil - }, - } - - migrationAbortCommand = cli.Command{ - Name: "migration-abort", - Category: "Region migrations", - Flags: []cli.Flag{&appFlag}, - Usage: "Abort a migration", - ArgsUsage: "migration-id", - Description: CommandDescription{ - Description: "Abort a running migration", - Examples: []string{"scalingo --app my-app migration-abort migration-id"}, - }.Render(), - Action: func(c *cli.Context) error { - if c.Args().Len() != 1 { - return cli.ShowCommandHelp(c, "migration-abort") - } - - migrationID := c.Args().First() - currentApp := detect.CurrentApp(c) - utils.CheckForConsent(c.Context, currentApp) - - err := regionmigrations.Abort(c.Context, currentApp, migrationID) - if err != nil { - errorQuit(c.Context, err) - } - return nil - }, - } - - migrationListCommand = cli.Command{ - Name: "migrations", - Category: "Region migrations", - Flags: []cli.Flag{&appFlag}, - Usage: "List all migrations linked to an app", - Description: CommandDescription{ - Description: "List all migrations linked to an app", - Examples: []string{"scalingo --app my-app migrations"}, - }.Render(), - Action: func(c *cli.Context) error { - currentApp := detect.CurrentApp(c) - - err := regionmigrations.List(c.Context, currentApp) - if err != nil { - errorQuit(c.Context, err) - } - return nil - }, - } - - migrationFollowCommand = cli.Command{ - Name: "migration-follow", - Category: "Region migrations", - Flags: []cli.Flag{&appFlag}, - Usage: "Follow a running migration", - ArgsUsage: "migration-id", - Description: CommandDescription{ - Description: "Listen for new events on a migration", - Examples: []string{"scalingo --app my-app migration-follow migration-id"}, - }.Render(), - Action: func(c *cli.Context) error { - currentApp := detect.CurrentApp(c) - - if c.Args().Len() != 1 { - cli.ShowCommandHelp(c, "migration-follow") - return nil - } - - migrationID := c.Args().First() - - err := regionmigrations.Follow(c.Context, currentApp, migrationID) - if err != nil { - errorQuit(c.Context, err) - } - return nil - }, - BashComplete: func(c *cli.Context) { - autocomplete.RegionMigrationsAutoComplete(c) - }, - } -) diff --git a/go.mod b/go.mod index 8a261e7de..a0f9e01f4 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,6 @@ module github.com/Scalingo/cli go 1.23.4 -toolchain go1.24.1 require ( github.com/AlecAivazis/survey/v2 v2.3.7 diff --git a/regionmigrations/confirmations.go b/regionmigrations/confirmations.go deleted file mode 100644 index 6b93b49cf..000000000 --- a/regionmigrations/confirmations.go +++ /dev/null @@ -1,69 +0,0 @@ -package regionmigrations - -import ( - "fmt" - - "github.com/AlecAivazis/survey/v2" - - "github.com/Scalingo/go-scalingo/v7" -) - -func ConfirmPrepare(migration scalingo.RegionMigration) bool { - fmt.Printf("Note: Prepare will create an empty canvas. This won't modify your production application\n\n") - fmt.Println("The following operations will be achieved:") - fmt.Println(" - Mark your app as migrating (preventing you from modifying it)") - fmt.Printf(" - Create the new app named %s in the region '%s'\n", migration.DstAppName, migration.Destination) - fmt.Println(" - Import the last deployment") - fmt.Println(" - Import environment variables") - fmt.Println(" - Import SCM configuration") - fmt.Println(" - Import collaborators") - fmt.Println(" - Import domains and TLS certificates") - fmt.Println(" - Import application settings") - fmt.Println(" - Import application formation") - fmt.Println(" - Import notifiers") - fmt.Println(" - Import autoscalers") - fmt.Println(" - Import alerts") - - return askContinue("Continue?") -} - -func ConfirmData(migration scalingo.RegionMigration) bool { - fmt.Println("The following operations will be achieved:") - fmt.Println(" - Stop your old app") - fmt.Printf(" - Create addons on the '%s' region\n", migration.Destination) - fmt.Println(" - Import addons data") - - return askContinue("Continue?") -} - -func ConfirmFinalize(migration scalingo.RegionMigration) bool { - fmt.Println("The following operations will be achieved:") - if migration.Status != scalingo.RegionMigrationStatusDataMigrated { - fmt.Println(" - Stop your old app") - } - fmt.Println(" - Start the new app") - fmt.Printf(" - Redirect the traffic coming to '%s' on the old region to '%s' on '%s'\n", migration.SrcAppName, migration.DstAppName, migration.Destination) - - return askContinue("Continue?") -} - -func askContinue(message string) bool { - result := false - prompt := &survey.Confirm{ - Message: "Continue?", - } - survey.AskOne(prompt, &result, nil) - return result -} - -func ConfirmStep(migration scalingo.RegionMigration, step scalingo.RegionMigrationStep) bool { - switch step { - case scalingo.RegionMigrationStepPrepare: - return ConfirmPrepare(migration) - case scalingo.RegionMigrationStepData: - return ConfirmData(migration) - case scalingo.RegionMigrationStepFinalize: - return ConfirmFinalize(migration) - } - return true -} diff --git a/regionmigrations/follow.go b/regionmigrations/follow.go deleted file mode 100644 index 204cc6105..000000000 --- a/regionmigrations/follow.go +++ /dev/null @@ -1,20 +0,0 @@ -package regionmigrations - -import ( - "context" - - errgo "gopkg.in/errgo.v1" - - "github.com/Scalingo/cli/config" -) - -func Follow(ctx context.Context, appID, migrationID string) error { - c, err := config.ScalingoClient(ctx) - if err != nil { - return errgo.Notef(err, "fail to get scalingo client") - } - - return WatchMigration(ctx, c, appID, migrationID, RefreshOpts{ - ShowHints: true, - }) -} diff --git a/regionmigrations/list.go b/regionmigrations/list.go deleted file mode 100644 index acf4d6bf7..000000000 --- a/regionmigrations/list.go +++ /dev/null @@ -1,56 +0,0 @@ -package regionmigrations - -import ( - "context" - "os" - "sort" - "time" - - "github.com/fatih/color" - "github.com/olekukonko/tablewriter" - errgo "gopkg.in/errgo.v1" - - "github.com/Scalingo/cli/config" - "github.com/Scalingo/cli/io" -) - -func List(ctx context.Context, appID string) error { - c, err := config.ScalingoClient(ctx) - if err != nil { - return errgo.Notef(err, "fail to get scalingo client") - } - - migrations, err := c.ListRegionMigrations(ctx, appID) - if err != nil { - return errgo.Notef(err, "fail to list migrations") - } - if len(migrations) == 0 { - io.Status("No migration found for this app") - return nil - } - - sort.Slice(migrations, func(i, j int) bool { - return migrations[i].StartedAt.Unix() > migrations[j].StartedAt.Unix() - }) - - t := tablewriter.NewWriter(os.Stdout) - t.SetHeader([]string{"ID", "Destination", "Started At", "Finished At", "Status"}) - - for _, migration := range migrations { - finishedAt := migration.FinishedAt.Local().Format(time.RFC822) - if migration.FinishedAt.IsZero() { - finishedAt = color.BlueString("Ongoing") - } - - t.Append([]string{ - migration.ID, - migration.Destination, - migration.StartedAt.Local().Format(time.RFC822), - finishedAt, - formatMigrationStatus(migration.Status), - }) - } - t.Render() - - return nil -} diff --git a/regionmigrations/refresher.go b/regionmigrations/refresher.go deleted file mode 100644 index 83c6a98b8..000000000 --- a/regionmigrations/refresher.go +++ /dev/null @@ -1,270 +0,0 @@ -package regionmigrations - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/briandowns/spinner" - "github.com/fatih/color" - "github.com/gosuri/uilive" - errgo "gopkg.in/errgo.v1" - - "github.com/Scalingo/cli/utils" - "github.com/Scalingo/go-scalingo/v7" - "github.com/Scalingo/go-utils/retry" -) - -type RefreshOpts struct { - ShowHints bool - ExpectedStatuses []scalingo.RegionMigrationStatus - HiddenSteps []string - CurrentStep scalingo.RegionMigrationStep -} - -type Refresher struct { - appID string - migrationID string - client *scalingo.Client - opts RefreshOpts - - lock *sync.Mutex - migration *scalingo.RegionMigration - errCount int - maxErrors int - stop bool - screenRefreshTime time.Duration - migrationRefreshTime time.Duration - wg *sync.WaitGroup - - currentLoadersStep int -} - -func NewRefresher(client *scalingo.Client, appID, migrationID string, opts RefreshOpts) *Refresher { - return &Refresher{ - appID: appID, - migrationID: migrationID, - client: client, - lock: &sync.Mutex{}, - migration: nil, - stop: false, - screenRefreshTime: 100 * time.Millisecond, - migrationRefreshTime: 1 * time.Second, - wg: &sync.WaitGroup{}, - currentLoadersStep: 0, - errCount: 0, - maxErrors: 0, - opts: opts, - } -} - -func (r *Refresher) Start() (*scalingo.RegionMigration, error) { - r.wg.Add(2) - go r.screenRefresher() - err := r.migrationRefresher() - r.wg.Wait() - if err != nil { - return r.migration, errgo.Notef(err, "fail to refresh migration") - } - return r.migration, nil -} - -func (r *Refresher) Stop() { - r.lock.Lock() - defer r.lock.Unlock() - r.stop = true -} - -func (r *Refresher) screenRefresher() { - defer r.wg.Done() - writer := uilive.New() - for { - r.lock.Lock() - stop := r.stop - migration := r.migration - errCount := r.errCount - maxErrors := r.maxErrors - r.lock.Unlock() - r.currentLoadersStep = (r.currentLoadersStep + 1) % len(spinner.CharSets[11]) - - r.writeMigration(writer, migration, errCount, maxErrors) - if stop { - return - } - - time.Sleep(r.screenRefreshTime) - } -} - -func (r *Refresher) onRefreshError(ctx context.Context, err error, currentAttempt, maxAttempt int) { - r.lock.Lock() - defer r.lock.Unlock() - r.errCount = currentAttempt + 1 - r.maxErrors = maxAttempt -} - -func (r *Refresher) migrationRefresher() error { - defer r.wg.Done() - r.lock.Lock() - client := r.client - r.lock.Unlock() - - errCount := 0 - - retrier := retry.New( - retry.WithMaxAttempts(10), - retry.WithWaitDuration(10*time.Second), - retry.WithErrorCallback(r.onRefreshError), - ) - - for { - err := retrier.Do(context.Background(), func(ctx context.Context) error { - migration, err := client.ShowRegionMigration(ctx, r.appID, r.migrationID) - if err != nil { - return err - } - r.lock.Lock() - r.errCount = 0 - r.migration = &migration - r.errCount = errCount - r.lock.Unlock() - return nil - }) - - if err != nil { - r.Stop() - return errgo.Notef(err, "fail to get migration") - } - - r.lock.Lock() - migration := r.migration - stop := r.stop - r.lock.Unlock() - if stop { - return nil - } - - if r.shouldStop(migration) { - r.Stop() - return nil - } - - time.Sleep(r.migrationRefreshTime) - } -} - -func (r *Refresher) writeMigration(w *uilive.Writer, migration *scalingo.RegionMigration, errCount, maxErrors int) { - defer w.Flush() - - if errCount != 0 { - fmt.Fprint(w.Newline(), color.RedString("Connection lost. Retrying (%v/%v)\n", errCount, maxErrors)) - } - - if migration == nil { - fmt.Fprint(w.Newline(), color.BlueString("%s Loading migration information\n", r.loader())) - return - } - - fmt.Fprintf(w.Newline(), "Migration ID: %s\n", migration.ID) - fmt.Fprintf(w.Newline(), "Migrating app: %s\n", migration.SrcAppName) - fmt.Fprintf(w.Newline(), "Destination: %s\n", migration.Destination) - if migration.NewAppID == "" { - fmt.Fprintf(w.Newline(), "New app ID: %s\n", color.BlueString("N/A")) - } else { - fmt.Fprintf(w.Newline(), "New app ID: %s\n", migration.NewAppID) - } - fmt.Fprintf(w.Newline(), "Status: %s\n", formatMigrationStatus(migration.Status)) - if r.opts.ShowHints { - fmt.Fprintf(w.Newline(), "%s\n", r.hintFor(migration)) - } - if migration.Status == scalingo.RegionMigrationStatusCreated { - fmt.Fprintf(w.Newline(), "%s Waiting for the migration to start\n", r.loader()) - } - - for _, step := range migration.Steps { - if r.shouldShowStep(step) { - r.writeStep(w, step) - } - } -} - -func (r *Refresher) writeStep(w *uilive.Writer, step scalingo.Step) { - result := "" - switch step.Status { - case scalingo.StepStatusRunning: - result = color.BlueString(fmt.Sprintf("%s %s...", r.loader(), step.Name)) - case scalingo.StepStatusDone: - result = color.GreenString(fmt.Sprintf("%s %s Done!", utils.Success, step.Name)) - case scalingo.StepStatusError: - result = color.RedString(fmt.Sprintf("%s %s FAILED!", utils.Error, step.Name)) - } - fmt.Fprintf(w.Newline(), "%s\n", result) -} - -func (r *Refresher) loader() string { - return spinner.CharSets[11][r.currentLoadersStep] -} - -func (r *Refresher) shouldStop(m *scalingo.RegionMigration) bool { - if m == nil { - return false - } - switch m.Status { - case scalingo.RegionMigrationStatusError: - return true - case scalingo.RegionMigrationStatusDone: - return true - } - - if r.opts.ExpectedStatuses == nil { - return false - } - - for _, status := range r.opts.ExpectedStatuses { - if m.Status == status { - return true - } - } - - return false -} - -func (r *Refresher) shouldShowStep(step scalingo.Step) bool { - if r.opts.HiddenSteps == nil { - return true - } - - for _, id := range r.opts.HiddenSteps { - if id == step.ID { - return false - } - } - return true -} - -func (r *Refresher) hintFor(m *scalingo.RegionMigration) string { - switch m.Status { - case scalingo.RegionMigrationStatusAborted: - return "The migration has been aborted. No update will be posted here." - case scalingo.RegionMigrationStatusCreated: - return "The migration has been created. The preflight checks will begin shortly." - case scalingo.RegionMigrationStatusPreflightError: - return "There was an error during the preflight checks. No update will be posted here." - case scalingo.RegionMigrationStatusPreflightSuccess: - return "The preflight checks were successful. Waiting on the user to start the 'prepare' step." - case scalingo.RegionMigrationStatusRunning: - return "The migration is currently running." - case scalingo.RegionMigrationStatusPrepared: - return "The migration has been prepared. Waiting on the user to start the 'data' or 'finalize' step." - case scalingo.RegionMigrationStatusDataMigrated: - return "The addon has been migrated. Waiting on the user to start the 'finalize' step." - case scalingo.RegionMigrationStatusError: - return "There was an error while running the migration. Waiting on the user to 'abort' it." - case scalingo.RegionMigrationStatusDone: - return "The migration is done. No update will be posted here." - case scalingo.RegionMigrationStatusAborting: - return "The migration will be aborted shortly. The abort process will begin shortly." - } - return "" -} diff --git a/regionmigrations/run.go b/regionmigrations/run.go deleted file mode 100644 index 87f54a19a..000000000 --- a/regionmigrations/run.go +++ /dev/null @@ -1,129 +0,0 @@ -package regionmigrations - -import ( - "context" - "fmt" - - errgo "gopkg.in/errgo.v1" - - "github.com/Scalingo/cli/config" - "github.com/Scalingo/go-scalingo/v7" -) - -func Create(ctx context.Context, app string, destination string, dstAppName string) error { - c, err := config.ScalingoClient(ctx) - if err != nil { - return errgo.Notef(err, "fail to get scalingo client") - } - - migration, err := c.CreateRegionMigration(ctx, app, scalingo.RegionMigrationParams{ - Destination: destination, - DstAppName: dstAppName, - }) - if err != nil { - return errgo.Notef(err, "fail to create migration") - } - - err = c.RunRegionMigrationStep(ctx, app, migration.ID, scalingo.RegionMigrationStepPreflight) - if err != nil { - return errgo.Notef(err, "fail to run preflight step") - } - - err = WatchMigration(ctx, c, app, migration.ID, RefreshOpts{ - ExpectedStatuses: []scalingo.RegionMigrationStatus{ - scalingo.RegionMigrationStatusPreflightError, - scalingo.RegionMigrationStatusPreflightSuccess, - }, - }) - if err != nil { - return errgo.Notef(err, "fail to watch migration") - } - - return nil -} - -func Run(ctx context.Context, app, migrationID string, step scalingo.RegionMigrationStep) error { - c, err := config.ScalingoClient(ctx) - if err != nil { - return errgo.Notef(err, "fail to get scalingo client") - } - - migration, err := c.ShowRegionMigration(ctx, app, migrationID) - if err != nil { - return errgo.Notef(err, "fail to show region migration") - } - - shouldContinue := ConfirmStep(migration, step) - if !shouldContinue { - fmt.Println("The current step has been canceled. You can restart it later.") - fmt.Println("If you want to abort the migration, run:") - fmt.Printf("scalingo --region %s --app %s migration-abort %s\n", migration.Source, app, migrationID) - return nil - } - expectedStatuses := []scalingo.RegionMigrationStatus{} - - switch step { - case scalingo.RegionMigrationStepPrepare: - expectedStatuses = append(expectedStatuses, scalingo.RegionMigrationStatusPrepared) - case scalingo.RegionMigrationStepData: - expectedStatuses = append(expectedStatuses, scalingo.RegionMigrationStatusDataMigrated) - } - - previousStepIDs := []string{} - - for _, step := range migration.Steps { - previousStepIDs = append(previousStepIDs, step.ID) - } - - err = c.RunRegionMigrationStep(ctx, app, migrationID, step) - if err != nil { - return errgo.Notef(err, "fail to run %s step", step) - } - - err = WatchMigration(ctx, c, app, migrationID, RefreshOpts{ - ExpectedStatuses: expectedStatuses, - HiddenSteps: previousStepIDs, - CurrentStep: step, - }) - if err != nil { - return errgo.Notef(err, "fail to watch migration") - } - - return nil -} - -func Abort(ctx context.Context, app, migrationID string) error { - c, err := config.ScalingoClient(ctx) - if err != nil { - return errgo.Notef(err, "fail to get scalingo client") - } - - migration, err := c.ShowRegionMigration(ctx, app, migrationID) - if err != nil { - return errgo.Notef(err, "fail to show region migration") - } - - previousStepIDs := []string{} - - for _, step := range migration.Steps { - previousStepIDs = append(previousStepIDs, step.ID) - } - - err = c.RunRegionMigrationStep(ctx, app, migrationID, scalingo.RegionMigrationStepAbort) - if err != nil { - return errgo.Notef(err, "fail to run abort step") - } - - err = WatchMigration(ctx, c, app, migrationID, RefreshOpts{ - ExpectedStatuses: []scalingo.RegionMigrationStatus{ - scalingo.RegionMigrationStatusAborted, - }, - HiddenSteps: previousStepIDs, - CurrentStep: scalingo.RegionMigrationStepAbort, - }) - if err != nil { - return errgo.Notef(err, "fail to watch migration") - } - - return nil -} diff --git a/regionmigrations/status.go b/regionmigrations/status.go deleted file mode 100644 index 2a157de73..000000000 --- a/regionmigrations/status.go +++ /dev/null @@ -1,139 +0,0 @@ -package regionmigrations - -import ( - "context" - "fmt" - "net" - "net/url" - - "github.com/fatih/color" - - "github.com/Scalingo/cli/config" - "github.com/Scalingo/cli/io" - "github.com/Scalingo/cli/utils" - "github.com/Scalingo/go-scalingo/v7" -) - -func showMigrationStatusSuccess(ctx context.Context, appID string, migration scalingo.RegionMigration) { - newRegionClient, err := config.ScalingoClientForRegion(ctx, migration.Destination) - if err != nil { - showGenericMigrationSuccessMessage() - return - } - - app, err := newRegionClient.AppsShow(ctx, appID) - if err != nil { - showGenericMigrationSuccessMessage() - return - } - - domains, err := newRegionClient.DomainsList(ctx, appID) - if err != nil { - showGenericMigrationSuccessMessage() - return - } - - color.Green("Your application is now available at: %s\n\n", app.BaseURL) - - io.Status("You will also need to change the Git URL of your repository.") - io.Info("To change the Git remote URL use:") - io.Infof("git remote set-url scalingo %s \n\n", app.GitURL) - - if len(domains) == 0 { - return - } - - dnsARecord := "" - dnsCNAMERecord := "" - parsed, err := url.Parse(app.BaseURL) - if err != nil { - io.Warning("You need to change the DNS record of your domains to point to the new region.") - io.Status("See: https://doc.scalingo.com/platform/app/domain#configure-your-domain-name for more informations") - return - } - dnsCNAMERecord = parsed.Host - addresses, err := net.LookupHost(parsed.Host) - if err == nil && len(addresses) > 0 { - dnsARecord = addresses[0] - } - - showDoc := false - io.Status("You need to make the following changes to your DNS records:\n") - for _, domain := range domains { - isCNAME, err := utils.IsCNAME(domain.Name) - if err != nil || (!isCNAME && dnsARecord == "") { - io.Infof("- %s record should be changed\n", domain.Name) - showDoc = true - } - if isCNAME { - io.Infof("- CNAME record of %s should be changed to %s\n", domain.Name, dnsCNAMERecord) - } else { - io.Infof("- A record of %s should be changed to %s\n", domain.Name, dnsARecord) - } - } - - if showDoc { - io.Info("To configure your DNS record, check out our documentation:") - io.Info("- https://doc.scalingo.com/platform/app/domain#configure-your-domain-name") - } -} -func showGenericMigrationSuccessMessage() { - color.Green("The application has been migrated!\n") - fmt.Println("You need to change the DNS record of your domains to point to the new region.") - fmt.Println("See: https://doc.scalingo.com/platform/app/domain#configure-your-domain-name for more information") -} - -func showMigrationStatusFailed(appID string, migration scalingo.RegionMigration, opts RefreshOpts) { - color.Red("The migration failed because of the following errors:\n") - - for i := range migration.Steps { - step := migration.Steps[len(migration.Steps)-1-i] - if step.Status == scalingo.StepStatusError { - if step.Logs == "" { - color.Red("- Step %s failed\n", step.Name) - } else { - color.Red("- Step %s failed: %s\n", step.Name, step.Logs) - } - } - } - - if opts.CurrentStep != scalingo.RegionMigrationStepAbort && migration.Status != scalingo.RegionMigrationStatusPreflightError { - fmt.Println("To rollback your application to a working state, run:") - fmt.Printf("scalingo --region %s --app %s migration-abort %s\n\n", migration.Source, appID, migration.ID) - } - - fmt.Println("You can contact support@scalingo.com to troubleshoot this issue.") -} - -func showMigrationStatusPreflightSuccess(appID string, migration scalingo.RegionMigration) { - fmt.Printf("Your app can be migrated to the %s zone.\n\n", migration.Destination) - fmt.Printf("- Start the migration with:\n") - fmt.Printf("scalingo --region %s --app %s migration-run --prepare %s\n", migration.Source, appID, migration.ID) - fmt.Printf("- Abort the migration with:\n") - fmt.Printf("scalingo --region %s --app %s migration-abort %s\n", migration.Source, appID, migration.ID) -} - -func showMigrationStatusPrepared(appID string, migration scalingo.RegionMigration) { - fmt.Printf("Application on region '%s' has been prepared, you can now:\n", migration.Destination) - fmt.Printf("- Let us migrate your data to '%s' newly created databases with:\n", migration.Destination) - fmt.Printf("scalingo --region %s --app %s migration-run --data %s\n", migration.Source, appID, migration.ID) - fmt.Printf("- Handle data migration manually, then finalizing the migration with:\n") - fmt.Printf("scalingo --region %s --app %s migration-run --finalize %s\n", migration.Source, appID, migration.ID) -} - -func showMigrationStatusDataMigrated(appID string, migration scalingo.RegionMigration) { - fmt.Printf("Data has been migrated to the '%s' region\n", migration.Destination) - fmt.Printf("You can finalize the migration with:\n") - fmt.Printf("scalingo --region %s --app %s migration-run --finalize %s\n", migration.Source, appID, migration.ID) -} - -func showMigrationStatusAborted(appID string, migration scalingo.RegionMigration) { - fmt.Printf("The migration '%s' has been aborted\n", migration.ID) - fmt.Printf("You can retry it with:\n") - fmt.Printf("scalingo --region %s --app %s migration-create --to %s", migration.Source, appID, migration.Destination) - if migration.DstAppName != migration.SrcAppName { - fmt.Printf(" --new-name %s \n", migration.DstAppName) - } else { - fmt.Printf("\n") - } -} diff --git a/regionmigrations/utils.go b/regionmigrations/utils.go deleted file mode 100644 index 9a4b663fa..000000000 --- a/regionmigrations/utils.go +++ /dev/null @@ -1,35 +0,0 @@ -package regionmigrations - -import ( - "github.com/fatih/color" - - "github.com/Scalingo/go-scalingo/v7" -) - -func formatMigrationStatus(status scalingo.RegionMigrationStatus) string { - strStatus := string(status) - switch status { - case scalingo.RegionMigrationStatusPrepared: - fallthrough - case scalingo.RegionMigrationStatusDataMigrated: - fallthrough - case scalingo.RegionMigrationStatusAborting: - fallthrough - case scalingo.RegionMigrationStatusCreated: - return color.BlueString(strStatus) - case scalingo.RegionMigrationStatusRunning: - return color.YellowString(strStatus) - case scalingo.RegionMigrationStatusPreflightSuccess: - fallthrough - case scalingo.RegionMigrationStatusDone: - return color.GreenString(strStatus) - case scalingo.RegionMigrationStatusPreflightError: - fallthrough - case scalingo.RegionMigrationStatusAborted: - fallthrough - case scalingo.RegionMigrationStatusError: - return color.RedString(strStatus) - } - - return color.BlueString(strStatus) -} diff --git a/regionmigrations/watch.go b/regionmigrations/watch.go deleted file mode 100644 index e91ab4d69..000000000 --- a/regionmigrations/watch.go +++ /dev/null @@ -1,46 +0,0 @@ -package regionmigrations - -import ( - "context" - "fmt" - - errgo "gopkg.in/errgo.v1" - - "github.com/Scalingo/go-scalingo/v7" -) - -func WatchMigration(ctx context.Context, client *scalingo.Client, appID, migrationID string, opts RefreshOpts) error { - refresher := NewRefresher(client, appID, migrationID, opts) - migration, err := refresher.Start() - if err != nil { - return errgo.Notef(err, "fail to watch migration") - } - - if migration == nil { - return nil - } - - migrationFinished(ctx, appID, *migration, opts) - - return nil -} - -func migrationFinished(ctx context.Context, appID string, migration scalingo.RegionMigration, opts RefreshOpts) { - fmt.Printf("\n\n") - switch migration.Status { - case scalingo.RegionMigrationStatusDone: - showMigrationStatusSuccess(ctx, appID, migration) - case scalingo.RegionMigrationStatusError: - fallthrough - case scalingo.RegionMigrationStatusPreflightError: - showMigrationStatusFailed(appID, migration, opts) - case scalingo.RegionMigrationStatusPreflightSuccess: - showMigrationStatusPreflightSuccess(appID, migration) - case scalingo.RegionMigrationStatusPrepared: - showMigrationStatusPrepared(appID, migration) - case scalingo.RegionMigrationStatusDataMigrated: - showMigrationStatusDataMigrated(appID, migration) - case scalingo.RegionMigrationStatusAborted: - showMigrationStatusAborted(appID, migration) - } -}