Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions internal/cmd/group_aws_migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package cmd

import (
"context"
"fmt"
"time"

"github.com/spf13/cobra"
"github.com/tursodatabase/turso-cli/internal"
"github.com/tursodatabase/turso-cli/internal/prompt"
)

var groupAwsMigrationCmd = &cobra.Command{
Use: "aws-migration",
Short: "Manage AWS migration of the group",
}

func init() {
groupCmd.AddCommand(groupAwsMigrationCmd)
groupAwsMigrationCmd.AddCommand(groupAwsMigrationInfoCmd)
groupAwsMigrationCmd.AddCommand(groupAwsMigrationStartCmd)
groupAwsMigrationCmd.AddCommand(groupAwsMigrationAbortCmd)
}

var groupAwsMigrationInfoCmd = &cobra.Command{
Use: "info <group-name>",
Short: "Migration status for the group",
Args: cobra.ExactArgs(1),
ValidArgsFunction: noFilesArg,
RunE: func(cmd *cobra.Command, args []string) error {
group := args[0]
if group == "" {
return fmt.Errorf("the first argument must contain a group name")
}

cmd.SilenceUsage = true
client, err := authedTursoClient()
if err != nil {
return err
}

_, err = client.Groups.Get(group)
if err != nil {
return err
}

info, err := client.Groups.GetAwsMigrationInfo(group)
if err != nil {
return err
}

if info.Status == "pending" {
fmt.Printf("AWS migration is %v\n%v\n", internal.Emph("in progress"), info.Comment)
} else if info.Status == "finished" {
fmt.Printf("AWS migration is %v\n", internal.Emph("finished"))
} else if info.Status == "aborted" {
fmt.Printf("AWS migration was %v\n", internal.Emph("aborted"))
} else if info.Status == "none" {
fmt.Printf("AWS migration is %v\n", internal.Emph("not started"))
}

return nil
},
}

var groupAwsMigrationStartCmd = &cobra.Command{
Use: "start <group-name>",
Short: "Start AWS migration process of the group",
Args: cobra.ExactArgs(1),
ValidArgsFunction: noFilesArg,
RunE: func(cmd *cobra.Command, args []string) error {
group := args[0]
if group == "" {
return fmt.Errorf("the first argument must contain a group name")
}

cmd.SilenceUsage = true
client, err := authedTursoClient()
if err != nil {
return err
}

_, err = client.Groups.Get(group)
if err != nil {
return err
}

info, err := client.Groups.GetAwsMigrationInfo(group)
if err != nil {
return err
}

if info.Status == "pending" {
fmt.Printf("AWS migration is %v\n%v\n", internal.Emph("in progress"), info.Comment)
return nil
} else if info.Status == "finished" {
fmt.Printf("AWS migration is %v\n", internal.Emph("finished"))
return nil
} else if info.Status == "aborted" {
fmt.Printf("AWS migration was %v\nPlease, contact with support@turso.tech for further assistance with group migration\n", internal.Emph("aborted"))
return nil
}

fmt.Printf("%v\n\n", info.Comment)

ok, err := promptConfirmation(fmt.Sprintf("Are you sure you want to migrate group %s from Fly to AWS?", internal.Emph(group)))
if err != nil {
return fmt.Errorf("could not get prompt confirmed by user: %w", err)
}

if !ok {
fmt.Println("Group migration cancelled by the user.")
return nil
}

spinner := prompt.Spinner(fmt.Sprintf("AWS migration of group %v is in progress", group))
defer spinner.Stop()

err = client.Groups.StartAwsMigration(group)
if err != nil {
return err
}

ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Minute)
defer cancel()

for {
select {
case <-ctx.Done():
spinner.Stop()
fmt.Printf("AWS migration for group %v still on-going.\n\n"+
"You can check status of the migration with: `turso group migration info <group-name>`.\n"+
"If migrations for certain databases haven't started, you can abort the group migration with: `turso group migration abort <group-name>`.\n\n"+
"Contact support@turso.tech in case of any issues\n", internal.Emph(group))
return nil
case <-time.NewTimer(5 * time.Second).C:
info, err := client.Groups.GetAwsMigrationInfo(group)
if err != nil {
return err
}
if info.Status == "finished" {
spinner.Stop()
fmt.Printf("Group %v was successfully migrated from Fly to AWS", internal.Emph(group))
return nil
} else {
spinner.Text(info.Comment)
}
}
}
},
}

var groupAwsMigrationAbortCmd = &cobra.Command{
Use: "abort <group-name>",
Short: "Abort AWS migration process of the group",
Args: cobra.ExactArgs(1),
ValidArgsFunction: noFilesArg,
RunE: func(cmd *cobra.Command, args []string) error {
group := args[0]
if group == "" {
return fmt.Errorf("the first argument must contain a group name")
}

cmd.SilenceUsage = true
client, err := authedTursoClient()
if err != nil {
return err
}

_, err = client.Groups.Get(group)
if err != nil {
return err
}

info, err := client.Groups.GetAwsMigrationInfo(group)
if err != nil {
return err
}

if info.Status == "none" {
fmt.Printf("AWS migration is %v", internal.Emph("not started"))
return nil
} else if info.Status == "aborted" {
fmt.Printf("AWS migration was already %v", internal.Emph("aborted"))
return nil
} else if info.Status == "finished" {
fmt.Printf("AWS migration was already %v", internal.Emph("finished"))
return nil
}

ok, err := promptConfirmation(fmt.Sprintf("Are you sure you want to abort group migration %s from Fly to AWS?", internal.Emph(group)))
if err != nil {
return fmt.Errorf("could not get prompt confirmed by user: %w", err)
}

if !ok {
fmt.Println("Group migration abort cancelled by the user.")
return nil
}

spinner := prompt.Spinner(fmt.Sprintf("AWS migration of group %v aborted", group))
defer spinner.Stop()

return client.Groups.AbortAwsMigration(group)
},
}
54 changes: 54 additions & 0 deletions internal/turso/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,60 @@ func (d *GroupsClient) Transfer(group string, to string) error {
return nil
}

type AwsMigrationInfo struct {
Status string `json:"status"`
Comment string `json:"comment"`
}

func (d *GroupsClient) GetAwsMigrationInfo(group string) (AwsMigrationInfo, error) {
url := d.URL(fmt.Sprintf("/%s/aws/migration/info", group))
r, err := d.client.Get(url, nil)
if err != nil {
return AwsMigrationInfo{}, fmt.Errorf("failed to get group migration info: %w", err)
}
defer r.Body.Close()

if r.StatusCode != http.StatusOK {
err := parseResponseError(r)
return AwsMigrationInfo{}, fmt.Errorf("failed to get group migration info: %w", err)
}
result, err := unmarshal[AwsMigrationInfo](r)
if err != nil {
return AwsMigrationInfo{}, fmt.Errorf("failed to parse group migration info: %w", err)
}
return result, nil
}

func (d *GroupsClient) StartAwsMigration(group string) error {
url := d.URL(fmt.Sprintf("/%s/aws/migration/start", group))
r, err := d.client.Post(url, nil)
if err != nil {
return fmt.Errorf("failed to start group migration: %w", err)
}
defer r.Body.Close()

if r.StatusCode != http.StatusOK {
err := parseResponseError(r)
return fmt.Errorf("failed to start group migration: %w", err)
}
return nil
}

func (d *GroupsClient) AbortAwsMigration(group string) error {
url := d.URL(fmt.Sprintf("/%s/aws/migration/abort", group))
r, err := d.client.Post(url, nil)
if err != nil {
return fmt.Errorf("failed to abort group migration: %w", err)
}
defer r.Body.Close()

if r.StatusCode != http.StatusOK {
err := parseResponseError(r)
return fmt.Errorf("failed to abort group migration: %w", err)
}
return nil
}

func (d *GroupsClient) URL(suffix string) string {
prefix := "/v1"
if d.client.Org != "" {
Expand Down