ipld-eth-server/vendor/github.com/ipfs/go-ipfs/repo/fsrepo/migrations/migrations.go
2019-12-02 13:24:46 -06:00

278 lines
6.1 KiB
Go

package mfsr
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
)
var DistPath = "https://ipfs.io/ipfs/QmZLYJBVBK8nqc5JTHp6CZU1v9Ja3MvGrkCe61PfCecc6E"
func init() {
if dist := os.Getenv("IPFS_DIST_PATH"); dist != "" {
DistPath = dist
}
}
const migrations = "fs-repo-migrations"
func migrationsBinName() string {
switch runtime.GOOS {
case "windows":
return migrations + ".exe"
default:
return migrations
}
}
func RunMigration(newv int) error {
migrateBin := migrationsBinName()
fmt.Println(" => Looking for suitable fs-repo-migrations binary.")
var err error
migrateBin, err = exec.LookPath(migrateBin)
if err == nil {
// check to make sure migrations binary supports our target version
err = verifyMigrationSupportsVersion(migrateBin, newv)
}
if err != nil {
fmt.Println(" => None found, downloading.")
loc, err := GetMigrations()
if err != nil {
fmt.Println(" => Failed to download fs-repo-migrations.")
return err
}
err = verifyMigrationSupportsVersion(loc, newv)
if err != nil {
return fmt.Errorf("no fs-repo-migration binary found for version %d: %s", newv, err)
}
migrateBin = loc
}
cmd := exec.Command(migrateBin, "-to", fmt.Sprint(newv), "-y")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf(" => Running: %s -to %d -y\n", migrateBin, newv)
err = cmd.Run()
if err != nil {
fmt.Printf(" => Failed: %s -to %d -y\n", migrateBin, newv)
return fmt.Errorf("migration failed: %s", err)
}
fmt.Printf(" => Success: fs-repo has been migrated to version %d.\n", newv)
return nil
}
func GetMigrations() (string, error) {
latest, err := GetLatestVersion(DistPath, migrations)
if err != nil {
return "", fmt.Errorf("failed to find latest fs-repo-migrations: %s", err)
}
dir, err := ioutil.TempDir("", "go-ipfs-migrate")
if err != nil {
return "", fmt.Errorf("failed to create fs-repo-migrations tempdir: %s", err)
}
out := filepath.Join(dir, migrationsBinName())
err = GetBinaryForVersion(migrations, migrations, DistPath, latest, out)
if err != nil {
return "", fmt.Errorf("failed to download latest fs-repo-migrations: %s", err)
}
err = os.Chmod(out, 0755)
if err != nil {
return "", err
}
return out, nil
}
func verifyMigrationSupportsVersion(fsrbin string, vn int) error {
sn, err := migrationsVersion(fsrbin)
if err != nil {
return err
}
if sn >= vn {
return nil
}
return fmt.Errorf("migrations binary doesnt support version %d: %s", vn, fsrbin)
}
func migrationsVersion(bin string) (int, error) {
out, err := exec.Command(bin, "-v").CombinedOutput()
if err != nil {
return 0, fmt.Errorf("failed to check migrations version: %s", err)
}
vs := strings.Trim(string(out), " \n\t")
vn, err := strconv.Atoi(vs)
if err != nil {
return 0, fmt.Errorf("migrations binary version check did not return a number: %s", err)
}
return vn, nil
}
func GetVersions(ipfspath, dist string) ([]string, error) {
rc, err := httpFetch(ipfspath + "/" + dist + "/versions")
if err != nil {
return nil, err
}
defer rc.Close()
var out []string
scan := bufio.NewScanner(rc)
for scan.Scan() {
out = append(out, scan.Text())
}
return out, nil
}
func GetLatestVersion(ipfspath, dist string) (string, error) {
vs, err := GetVersions(ipfspath, dist)
if err != nil {
return "", err
}
var latest string
for i := len(vs) - 1; i >= 0; i-- {
if !strings.Contains(vs[i], "-dev") {
latest = vs[i]
break
}
}
if latest == "" {
return "", fmt.Errorf("couldnt find a non dev version in the list")
}
return vs[len(vs)-1], nil
}
func httpGet(url string) (*http.Response, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("http.NewRequest error: %s", err)
}
req.Header.Set("User-Agent", "go-ipfs")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("http.DefaultClient.Do error: %s", err)
}
return resp, nil
}
func httpFetch(url string) (io.ReadCloser, error) {
resp, err := httpGet(url)
if err != nil {
return nil, err
}
if resp.StatusCode >= 400 {
mes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading error body: %s", err)
}
return nil, fmt.Errorf("GET %s error: %s: %s", url, resp.Status, string(mes))
}
return resp.Body, nil
}
func GetBinaryForVersion(distname, binnom, root, vers, out string) error {
dir, err := ioutil.TempDir("", "go-ipfs-auto-migrate")
if err != nil {
return err
}
var archive string
switch runtime.GOOS {
case "windows":
archive = "zip"
binnom += ".exe"
default:
archive = "tar.gz"
}
osv, err := osWithVariant()
if err != nil {
return err
}
finame := fmt.Sprintf("%s_%s_%s-%s.%s", distname, vers, osv, runtime.GOARCH, archive)
distpath := fmt.Sprintf("%s/%s/%s/%s", root, distname, vers, finame)
data, err := httpFetch(distpath)
if err != nil {
return err
}
arcpath := filepath.Join(dir, finame)
fi, err := os.Create(arcpath)
if err != nil {
return err
}
_, err = io.Copy(fi, data)
if err != nil {
return err
}
fi.Close()
return unpackArchive(distname, binnom, arcpath, out, archive)
}
// osWithVariant returns the OS name with optional variant.
// Currently returns either runtime.GOOS, or "linux-musl".
func osWithVariant() (string, error) {
if runtime.GOOS != "linux" {
return runtime.GOOS, nil
}
// ldd outputs the system's kind of libc.
// - on standard ubuntu: ldd (Ubuntu GLIBC 2.23-0ubuntu5) 2.23
// - on alpine: musl libc (x86_64)
//
// we use the combined stdout+stderr,
// because ldd --version prints differently on different OSes.
// - on standard ubuntu: stdout
// - on alpine: stderr (it probably doesn't know the --version flag)
//
// we suppress non-zero exit codes (see last point about alpine).
out, err := exec.Command("sh", "-c", "ldd --version || true").CombinedOutput()
if err != nil {
return "", err
}
// now just see if we can find "musl" somewhere in the output
scan := bufio.NewScanner(bytes.NewBuffer(out))
for scan.Scan() {
if strings.Contains(scan.Text(), "musl") {
return "linux-musl", nil
}
}
return "linux", nil
}