-
Notifications
You must be signed in to change notification settings - Fork 224
/
import.go
209 lines (183 loc) · 5.3 KB
/
import.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package postgres
import (
"context"
"fmt"
"os"
"github.com/docker/docker/pkg/ioutils"
"github.com/mattn/go-colorable"
"github.com/spf13/cobra"
"github.com/superfly/flyctl/agent"
"github.com/superfly/flyctl/api"
"github.com/superfly/flyctl/client"
"github.com/superfly/flyctl/internal/appconfig"
"github.com/superfly/flyctl/internal/command"
"github.com/superfly/flyctl/internal/command/apps"
"github.com/superfly/flyctl/internal/command/ssh"
"github.com/superfly/flyctl/internal/flag"
mach "github.com/superfly/flyctl/internal/machine"
"github.com/superfly/flyctl/internal/prompt"
)
func newImport() *cobra.Command {
const (
short = "Imports database from a specified Postgres URI"
long = short + "\n"
usage = "import <source-uri>"
)
cmd := command.New(usage, short, long, runImport,
command.RequireSession,
command.RequireAppName,
)
cmd.Args = cobra.ExactArgs(1)
flag.Add(cmd,
flag.App(),
flag.AppConfig(),
flag.String{
Name: "image",
Description: "Path to public image containing custom migration process",
},
flag.String{
Name: "vm-size",
Description: "the size of the VM",
},
flag.String{
Name: "region",
Description: "Region to provision migration machine",
},
flag.Bool{
Name: "no-owner",
Description: "Do not set ownership of objects to match the original database. Makes dump restorable by any user.",
Default: true,
},
flag.Bool{
Name: "create",
Description: "Begin by creating the database itself and reconnecting to it. If --clean is also specified, the script drops and recreates the target database before reconnecting to it.",
Default: true,
},
flag.Bool{
Name: "clean",
Description: "Drop database objects prior to creating them.",
Default: false,
},
flag.Bool{
Name: "data-only",
Description: "Dump only the data, not the schema (data definitions).",
},
)
return cmd
}
func runImport(ctx context.Context) error {
var (
client = client.FromContext(ctx).API()
appName = appconfig.NameFromContext(ctx)
sourceURI = flag.FirstArg(ctx)
machSize = flag.GetString(ctx, "vm-size")
imageRef = flag.GetString(ctx, "image")
)
// pre-fetch platform regions for later use
prompt.PlatformRegions(ctx)
// Resolve target app
app, err := client.GetAppCompact(ctx, appName)
if err != nil {
return fmt.Errorf("failed to resolve app: %w", err)
}
if app.PlatformVersion != "machines" {
return fmt.Errorf("This feature is only available on our Machines platform")
}
if !app.IsPostgresApp() {
return fmt.Errorf("The target app must be a Postgres app")
}
// Resolve region
region, err := prompt.Region(ctx, !app.Organization.PaidPlan, prompt.RegionParams{
Message: "Choose a region to deploy the migration machine:",
})
if err != nil {
return fmt.Errorf("failed to resolve region: %s", err)
}
// Resolve vm-size
vmSize, err := resolveVMSize(ctx, machSize)
if err != nil {
return err
}
// Set sourceURI as a secret
_, err = client.SetSecrets(ctx, app.Name, map[string]string{
"SOURCE_DATABASE_URI": sourceURI,
})
if err != nil {
return fmt.Errorf("failed to set secrets: %s", err)
}
ctx, err = apps.BuildContext(ctx, app)
if err != nil {
return fmt.Errorf("failed to build context: %s", err)
}
machineConfig := &api.MachineConfig{
Env: map[string]string{
"POSTGRES_PASSWORD": "pass",
},
Guest: &api.MachineGuest{
CPUKind: vmSize.CPUClass,
CPUs: int(vmSize.CPUCores),
MemoryMB: vmSize.MemoryMB,
},
DNS: &api.DNSConfig{
SkipRegistration: true,
},
Restart: api.MachineRestart{
Policy: api.MachineRestartPolicyNo,
},
AutoDestroy: true,
}
// If a custom migration image is not specified, resolve latest managed image.
if imageRef == "" {
imageRef, err = client.GetLatestImageTag(ctx, "flyio/postgres-importer", nil)
if err != nil {
return err
}
}
machineConfig.Image = imageRef
ephemeralInput := &mach.EphemeralInput{
LaunchInput: api.LaunchMachineInput{
Region: region.Code,
Config: machineConfig,
},
What: "to run the import process",
}
// Create ephemeral machine
machine, cleanup, err := mach.LaunchEphemeral(ctx, ephemeralInput)
if err != nil {
return err
}
defer cleanup()
// Initiate migration process
err = ssh.SSHConnect(&ssh.SSHParams{
Ctx: ctx,
Org: app.Organization,
Dialer: agent.DialerFromContext(ctx),
App: app.Name,
Username: ssh.DefaultSshUsername,
Cmd: resolveImportCommand(ctx),
Stdin: os.Stdin,
Stdout: ioutils.NewWriteCloserWrapper(colorable.NewColorableStdout(), func() error { return nil }),
Stderr: ioutils.NewWriteCloserWrapper(colorable.NewColorableStderr(), func() error { return nil }),
}, machine.PrivateIP)
if err != nil {
return fmt.Errorf("failed to run ssh: %s", err)
}
// Unset secret
_, err = client.UnsetSecrets(ctx, app.Name, []string{"SOURCE_DATABASE_URI"})
if err != nil {
return fmt.Errorf("failed to set secrets: %s", err)
}
return nil
}
func resolveImportCommand(ctx context.Context) string {
var (
noOwner = flag.GetBool(ctx, "no-owner")
create = flag.GetBool(ctx, "create")
clean = flag.GetBool(ctx, "clean")
dataOnly = flag.GetBool(ctx, "data-only")
)
return fmt.Sprintf(
"migrate -no-owner=%v -create=%v -clean=%v -data-only=%v",
noOwner, create, clean, dataOnly,
)
}