Skip to content

Commit 5a908fd

Browse files
authored
add AWS migration CLI command (#972)
Add two group level commands: 1. `turso group aws-migration info <group-name>` - shows status of the group migration process 2. `turso group aws-migration start <group-name>` - start migration process for selected group 3. `turso group aws-migration abort <group-name>` - abort migration process for selected group
2 parents 5ea0d80 + 0fa8e1f commit 5a908fd

File tree

2 files changed

+260
-0
lines changed

2 files changed

+260
-0
lines changed

internal/cmd/group_aws_migrate.go

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/spf13/cobra"
9+
"github.com/tursodatabase/turso-cli/internal"
10+
"github.com/tursodatabase/turso-cli/internal/prompt"
11+
)
12+
13+
var groupAwsMigrationCmd = &cobra.Command{
14+
Use: "aws-migration",
15+
Short: "Manage AWS migration of the group",
16+
}
17+
18+
func init() {
19+
groupCmd.AddCommand(groupAwsMigrationCmd)
20+
groupAwsMigrationCmd.AddCommand(groupAwsMigrationInfoCmd)
21+
groupAwsMigrationCmd.AddCommand(groupAwsMigrationStartCmd)
22+
groupAwsMigrationCmd.AddCommand(groupAwsMigrationAbortCmd)
23+
}
24+
25+
var groupAwsMigrationInfoCmd = &cobra.Command{
26+
Use: "info <group-name>",
27+
Short: "Migration status for the group",
28+
Args: cobra.ExactArgs(1),
29+
ValidArgsFunction: noFilesArg,
30+
RunE: func(cmd *cobra.Command, args []string) error {
31+
group := args[0]
32+
if group == "" {
33+
return fmt.Errorf("the first argument must contain a group name")
34+
}
35+
36+
cmd.SilenceUsage = true
37+
client, err := authedTursoClient()
38+
if err != nil {
39+
return err
40+
}
41+
42+
_, err = client.Groups.Get(group)
43+
if err != nil {
44+
return err
45+
}
46+
47+
info, err := client.Groups.GetAwsMigrationInfo(group)
48+
if err != nil {
49+
return err
50+
}
51+
52+
if info.Status == "pending" {
53+
fmt.Printf("AWS migration is %v\n%v\n", internal.Emph("in progress"), info.Comment)
54+
} else if info.Status == "finished" {
55+
fmt.Printf("AWS migration is %v\n", internal.Emph("finished"))
56+
} else if info.Status == "aborted" {
57+
fmt.Printf("AWS migration was %v\n", internal.Emph("aborted"))
58+
} else if info.Status == "none" {
59+
fmt.Printf("AWS migration is %v\n", internal.Emph("not started"))
60+
}
61+
62+
return nil
63+
},
64+
}
65+
66+
var groupAwsMigrationStartCmd = &cobra.Command{
67+
Use: "start <group-name>",
68+
Short: "Start AWS migration process of the group",
69+
Args: cobra.ExactArgs(1),
70+
ValidArgsFunction: noFilesArg,
71+
RunE: func(cmd *cobra.Command, args []string) error {
72+
group := args[0]
73+
if group == "" {
74+
return fmt.Errorf("the first argument must contain a group name")
75+
}
76+
77+
cmd.SilenceUsage = true
78+
client, err := authedTursoClient()
79+
if err != nil {
80+
return err
81+
}
82+
83+
_, err = client.Groups.Get(group)
84+
if err != nil {
85+
return err
86+
}
87+
88+
info, err := client.Groups.GetAwsMigrationInfo(group)
89+
if err != nil {
90+
return err
91+
}
92+
93+
if info.Status == "pending" {
94+
fmt.Printf("AWS migration is %v\n%v\n", internal.Emph("in progress"), info.Comment)
95+
return nil
96+
} else if info.Status == "finished" {
97+
fmt.Printf("AWS migration is %v\n", internal.Emph("finished"))
98+
return nil
99+
} else if info.Status == "aborted" {
100+
fmt.Printf("AWS migration was %v\nPlease, contact with support@turso.tech for further assistance with group migration\n", internal.Emph("aborted"))
101+
return nil
102+
}
103+
104+
fmt.Printf("%v\n\n", info.Comment)
105+
106+
ok, err := promptConfirmation(fmt.Sprintf("Are you sure you want to migrate group %s from Fly to AWS?", internal.Emph(group)))
107+
if err != nil {
108+
return fmt.Errorf("could not get prompt confirmed by user: %w", err)
109+
}
110+
111+
if !ok {
112+
fmt.Println("Group migration cancelled by the user.")
113+
return nil
114+
}
115+
116+
spinner := prompt.Spinner(fmt.Sprintf("AWS migration of group %v is in progress", group))
117+
defer spinner.Stop()
118+
119+
err = client.Groups.StartAwsMigration(group)
120+
if err != nil {
121+
return err
122+
}
123+
124+
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Minute)
125+
defer cancel()
126+
127+
for {
128+
select {
129+
case <-ctx.Done():
130+
spinner.Stop()
131+
fmt.Printf("AWS migration for group %v still on-going.\n\n"+
132+
"You can check status of the migration with: `turso group migration info <group-name>`.\n"+
133+
"If migrations for certain databases haven't started, you can abort the group migration with: `turso group migration abort <group-name>`.\n\n"+
134+
"Contact support@turso.tech in case of any issues\n", internal.Emph(group))
135+
return nil
136+
case <-time.NewTimer(5 * time.Second).C:
137+
info, err := client.Groups.GetAwsMigrationInfo(group)
138+
if err != nil {
139+
return err
140+
}
141+
if info.Status == "finished" {
142+
spinner.Stop()
143+
fmt.Printf("Group %v was successfully migrated from Fly to AWS", internal.Emph(group))
144+
return nil
145+
} else {
146+
spinner.Text(info.Comment)
147+
}
148+
}
149+
}
150+
},
151+
}
152+
153+
var groupAwsMigrationAbortCmd = &cobra.Command{
154+
Use: "abort <group-name>",
155+
Short: "Abort AWS migration process of the group",
156+
Args: cobra.ExactArgs(1),
157+
ValidArgsFunction: noFilesArg,
158+
RunE: func(cmd *cobra.Command, args []string) error {
159+
group := args[0]
160+
if group == "" {
161+
return fmt.Errorf("the first argument must contain a group name")
162+
}
163+
164+
cmd.SilenceUsage = true
165+
client, err := authedTursoClient()
166+
if err != nil {
167+
return err
168+
}
169+
170+
_, err = client.Groups.Get(group)
171+
if err != nil {
172+
return err
173+
}
174+
175+
info, err := client.Groups.GetAwsMigrationInfo(group)
176+
if err != nil {
177+
return err
178+
}
179+
180+
if info.Status == "none" {
181+
fmt.Printf("AWS migration is %v", internal.Emph("not started"))
182+
return nil
183+
} else if info.Status == "aborted" {
184+
fmt.Printf("AWS migration was already %v", internal.Emph("aborted"))
185+
return nil
186+
} else if info.Status == "finished" {
187+
fmt.Printf("AWS migration was already %v", internal.Emph("finished"))
188+
return nil
189+
}
190+
191+
ok, err := promptConfirmation(fmt.Sprintf("Are you sure you want to abort group migration %s from Fly to AWS?", internal.Emph(group)))
192+
if err != nil {
193+
return fmt.Errorf("could not get prompt confirmed by user: %w", err)
194+
}
195+
196+
if !ok {
197+
fmt.Println("Group migration abort cancelled by the user.")
198+
return nil
199+
}
200+
201+
spinner := prompt.Spinner(fmt.Sprintf("AWS migration of group %v aborted", group))
202+
defer spinner.Stop()
203+
204+
return client.Groups.AbortAwsMigration(group)
205+
},
206+
}

internal/turso/groups.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,60 @@ func (d *GroupsClient) Transfer(group string, to string) error {
381381
return nil
382382
}
383383

384+
type AwsMigrationInfo struct {
385+
Status string `json:"status"`
386+
Comment string `json:"comment"`
387+
}
388+
389+
func (d *GroupsClient) GetAwsMigrationInfo(group string) (AwsMigrationInfo, error) {
390+
url := d.URL(fmt.Sprintf("/%s/aws/migration/info", group))
391+
r, err := d.client.Get(url, nil)
392+
if err != nil {
393+
return AwsMigrationInfo{}, fmt.Errorf("failed to get group migration info: %w", err)
394+
}
395+
defer r.Body.Close()
396+
397+
if r.StatusCode != http.StatusOK {
398+
err := parseResponseError(r)
399+
return AwsMigrationInfo{}, fmt.Errorf("failed to get group migration info: %w", err)
400+
}
401+
result, err := unmarshal[AwsMigrationInfo](r)
402+
if err != nil {
403+
return AwsMigrationInfo{}, fmt.Errorf("failed to parse group migration info: %w", err)
404+
}
405+
return result, nil
406+
}
407+
408+
func (d *GroupsClient) StartAwsMigration(group string) error {
409+
url := d.URL(fmt.Sprintf("/%s/aws/migration/start", group))
410+
r, err := d.client.Post(url, nil)
411+
if err != nil {
412+
return fmt.Errorf("failed to start group migration: %w", err)
413+
}
414+
defer r.Body.Close()
415+
416+
if r.StatusCode != http.StatusOK {
417+
err := parseResponseError(r)
418+
return fmt.Errorf("failed to start group migration: %w", err)
419+
}
420+
return nil
421+
}
422+
423+
func (d *GroupsClient) AbortAwsMigration(group string) error {
424+
url := d.URL(fmt.Sprintf("/%s/aws/migration/abort", group))
425+
r, err := d.client.Post(url, nil)
426+
if err != nil {
427+
return fmt.Errorf("failed to abort group migration: %w", err)
428+
}
429+
defer r.Body.Close()
430+
431+
if r.StatusCode != http.StatusOK {
432+
err := parseResponseError(r)
433+
return fmt.Errorf("failed to abort group migration: %w", err)
434+
}
435+
return nil
436+
}
437+
384438
func (d *GroupsClient) URL(suffix string) string {
385439
prefix := "/v1"
386440
if d.client.Org != "" {

0 commit comments

Comments
 (0)