-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
migrations.go
214 lines (189 loc) · 5.49 KB
/
migrations.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
210
211
212
213
214
package migrations
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"runtime"
"strings"
"sync"
)
const (
// Migrations subdirectory in distribution. Empty for root (no subdir).
distMigsRoot = ""
distFSRM = "fs-repo-migrations"
)
// RunMigration finds, downloads, and runs the individual migrations needed to
// migrate the repo from its current version to the target version.
func RunMigration(ctx context.Context, fetcher Fetcher, targetVer int, ipfsDir string, allowDowngrade bool) error {
ipfsDir, err := CheckIpfsDir(ipfsDir)
if err != nil {
return err
}
fromVer, err := RepoVersion(ipfsDir)
if err != nil {
return fmt.Errorf("could not get repo version: %s", err)
}
if fromVer == targetVer {
// repo already at target version number
return nil
}
if fromVer > targetVer && !allowDowngrade {
return fmt.Errorf("downgrade not allowed from %d to %d", fromVer, targetVer)
}
logger := log.New(os.Stdout, "", 0)
logger.Print("Looking for suitable migration binaries.")
migrations, binPaths, err := findMigrations(ctx, fromVer, targetVer)
if err != nil {
return err
}
// Download migrations that were not found
if len(binPaths) < len(migrations) {
missing := make([]string, 0, len(migrations)-len(binPaths))
for _, mig := range migrations {
if _, ok := binPaths[mig]; !ok {
missing = append(missing, mig)
}
}
logger.Println("Need", len(missing), "migrations, downloading.")
tmpDir, err := ioutil.TempDir("", "migrations")
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
fetched, err := fetchMigrations(ctx, fetcher, missing, tmpDir, logger)
if err != nil {
logger.Print("Failed to download migrations.")
return err
}
for i := range missing {
binPaths[missing[i]] = fetched[i]
}
}
var revert bool
if fromVer > targetVer {
revert = true
}
for _, migration := range migrations {
logger.Println("Running migration", migration, "...")
err = runMigration(ctx, binPaths[migration], ipfsDir, revert, logger)
if err != nil {
return fmt.Errorf("migration %s failed: %s", migration, err)
}
}
logger.Printf("Success: fs-repo migrated to version %d.\n", targetVer)
return nil
}
func NeedMigration(target int) (bool, error) {
vnum, err := RepoVersion("")
if err != nil {
return false, fmt.Errorf("could not get repo version: %s", err)
}
return vnum != target, nil
}
func ExeName(name string) string {
if runtime.GOOS == "windows" {
return name + ".exe"
}
return name
}
func migrationName(from, to int) string {
return fmt.Sprintf("fs-repo-%d-to-%d", from, to)
}
// findMigrations returns a list of migrations, ordered from first to last
// migration to apply, and a map of locations of migration binaries of any
// migrations that were found.
func findMigrations(ctx context.Context, from, to int) ([]string, map[string]string, error) {
step := 1
count := to - from
if from > to {
step = -1
count = from - to
}
migrations := make([]string, 0, count)
binPaths := make(map[string]string, count)
for cur := from; cur != to; cur += step {
if ctx.Err() != nil {
return nil, nil, ctx.Err()
}
var migName string
if step == -1 {
migName = migrationName(cur+step, cur)
} else {
migName = migrationName(cur, cur+step)
}
migrations = append(migrations, migName)
bin, err := exec.LookPath(migName)
if err != nil {
continue
}
binPaths[migName] = bin
}
return migrations, binPaths, nil
}
func runMigration(ctx context.Context, binPath, ipfsDir string, revert bool, logger *log.Logger) error {
pathArg := fmt.Sprintf("-path=%s", ipfsDir)
var cmd *exec.Cmd
if revert {
logger.Println(" => Running:", binPath, pathArg, "-verbose=true -revert")
cmd = exec.CommandContext(ctx, binPath, pathArg, "-verbose=true", "-revert")
} else {
logger.Println(" => Running:", binPath, pathArg, "-verbose=true")
cmd = exec.CommandContext(ctx, binPath, pathArg, "-verbose=true")
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// fetchMigrations downloads the requested migrations, and returns a slice with
// the paths of each binary, in the same order specified by needed.
func fetchMigrations(ctx context.Context, fetcher Fetcher, needed []string, destDir string, logger *log.Logger) ([]string, error) {
osv, err := osWithVariant()
if err != nil {
return nil, err
}
if osv == "linux-musl" {
return nil, fmt.Errorf("linux-musl not supported, you must build the binary from source for your platform")
}
var wg sync.WaitGroup
wg.Add(len(needed))
bins := make([]string, len(needed))
// Download and unpack all requested migrations concurrently.
for i, name := range needed {
logger.Printf("Downloading migration: %s...", name)
go func(i int, name string) {
defer wg.Done()
dist := path.Join(distMigsRoot, name)
ver, err := LatestDistVersion(ctx, fetcher, dist, false)
if err != nil {
logger.Printf("could not get latest version of migration %s: %s", name, err)
return
}
loc, err := FetchBinary(ctx, fetcher, dist, ver, name, destDir)
if err != nil {
logger.Printf("could not download %s: %s", name, err)
return
}
logger.Printf("Downloaded and unpacked migration: %s (%s)", loc, ver)
bins[i] = loc
}(i, name)
}
wg.Wait()
var fails []string
for i := range bins {
if bins[i] == "" {
fails = append(fails, needed[i])
}
}
if len(fails) != 0 {
err = fmt.Errorf("failed to download migrations: %s", strings.Join(fails, " "))
if ctx.Err() != nil {
err = fmt.Errorf("%s, %s", ctx.Err(), err)
}
return nil, err
}
return bins, nil
}