diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index b8b28109e..cbd1e7967 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -159,6 +159,10 @@ "ImportPath": "github.com/jbenet/go-logging", "Rev": "74bec4b83f6d45d1402c1e9d94c0c29e39f6e0ea" }, + { + "ImportPath": "github.com/jbenet/go-migrate", + "Rev": "593be6b4b24a87e4d380e54339721ad4b4c6543c" + }, { "ImportPath": "github.com/jbenet/go-msgio", "Rev": "dbae89193876910c736b2ce1291fa8bbcf299d77" diff --git a/Godeps/_workspace/src/github.com/jbenet/go-migrate/LICENSE b/Godeps/_workspace/src/github.com/jbenet/go-migrate/LICENSE new file mode 100644 index 000000000..c7386b3c9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-migrate/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Juan Batiz-Benet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/jbenet/go-migrate/README.md b/Godeps/_workspace/src/github.com/jbenet/go-migrate/README.md new file mode 100644 index 000000000..8ddf5a6d3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-migrate/README.md @@ -0,0 +1,11 @@ +# go-migrate + +This is a very simple migration framework. See "Migrations" in https://github.com/jbenet/random-ideas/issues/33 + +This package includes: + +- `migrate` package -- lib to write migration programs + +## The model + +The idea here is that we have some thing -- usually a directory -- that needs to be migrated between different representation versions. This may be because there has been an upgrade. diff --git a/Godeps/_workspace/src/github.com/jbenet/go-migrate/cli.go b/Godeps/_workspace/src/github.com/jbenet/go-migrate/cli.go new file mode 100644 index 000000000..66f1f3b2b --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-migrate/cli.go @@ -0,0 +1,51 @@ +package migrate + +import ( + "flag" + "fmt" + "os" +) + +type Flags struct { + Force bool + Revert bool + Path string // file path to migrate for fs based migrations +} + +func (f *Flags) Setup() { + flag.BoolVar(&f.Force, "f", false, "whether to force a migration (ignores warnings)") + flag.BoolVar(&f.Revert, "revert", false, "whether to apply the migration backwards") + flag.StringVar(&f.Path, "path", "", "file path to migrate for fs based migrations") +} + +func (f *Flags) Parse() { + flag.Parse() +} + +func Run(m Migration) error { + f := Flags{} + f.Setup() + f.Parse() + + if !m.Reversible() { + if f.Revert { + return fmt.Errorf("migration %d is irreversible", m.Versions()) + } + if !f.Force { + return fmt.Errorf("migration %d is irreversible (use -f to proceed)", m.Versions()) + } + } + + if f.Revert { + return m.Revert(Options{f}) + } else { + return m.Apply(Options{f}) + } +} + +func Main(m Migration) { + if err := Run(m); err != nil { + fmt.Fprintf(os.Stderr, "error: %s\n", err) + os.Exit(1) + } +} diff --git a/Godeps/_workspace/src/github.com/jbenet/go-migrate/doc.go b/Godeps/_workspace/src/github.com/jbenet/go-migrate/doc.go new file mode 100644 index 000000000..29e607a13 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-migrate/doc.go @@ -0,0 +1,2 @@ +// Package migrate is used to write migrations between representations of things. +package migrate diff --git a/Godeps/_workspace/src/github.com/jbenet/go-migrate/migrate.go b/Godeps/_workspace/src/github.com/jbenet/go-migrate/migrate.go new file mode 100644 index 000000000..f374da273 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-migrate/migrate.go @@ -0,0 +1,37 @@ +package migrate + +import ( + "fmt" +) + +// Options are migration options. For now all flags are options. +type Options struct { + Flags +} + +// Migration represents +type Migration interface { + + // Versions is the "v-to-v" version string. + Versions() string + + // Reversible returns whether this migration can be reverted. + // Endeavor to make them all reversible. This is here only to warn users + // in case this is not the case. + Reversible() bool + + // Apply applies the migration in question. + Apply(Options) error + + // Revert un-applies the migration in question. This should be best-effort. + // Some migrations are definitively one-way. If so, return an error. + Revert(Options) error +} + +func SplitVersion(s string) (from int, to int) { + _, err := fmt.Scanf(s, "%d-to-%d", &from, &to) + if err != nil { + panic(err.Error()) + } + return +} diff --git a/repo/fsrepo/migrations/0-to-1/main.go b/repo/fsrepo/migrations/0-to-1/main.go new file mode 100644 index 000000000..cc0c5ef1c --- /dev/null +++ b/repo/fsrepo/migrations/0-to-1/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "fmt" + "os" + "strings" + + migrate "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-migrate" + mfsr "github.com/ipfs/go-ipfs/repo/fsrepo/migrations" +) + +type migration struct { +} + +// Version is the int version number. This could be a string +// in future versions +func (m migration) Versions() string { + return "0-to-1" +} + +// Reversible returns whether this migration can be reverted. +// Endeavor to make them all reversible. This is here only to warn users +// in case this is not the case. +func (m migration) Reversible() bool { + return true +} + +// Apply applies the migration in question. +// This migration merely adds a version file. +func (m migration) Apply(opts migrate.Options) error { + repo := mfsr.RepoPath(opts.Path) + + // first, check if there is a version file. + // if there is, bail out. + if v, err := repo.Version(); err == nil { + return fmt.Errorf("repo at %s is version %s (not 0)", opts.Path, v) + } else if !strings.Contains(err.Error(), "no version file in repo") { + return err + } + + // add the version file + if err := repo.WriteVersion("1"); err != nil { + return err + } + + return nil +} + +// Revert un-applies the migration in question. This should be best-effort. +// Some migrations are definitively one-way. If so, return an error. +func (m migration) Revert(opts migrate.Options) error { + repo := mfsr.RepoPath(opts.Path) + + if err := repo.CheckVersion("1"); err != nil { + return err + } + + // remove the version file + if err := os.Remove(repo.VersionFile()); err != nil { + return err + } + + return nil +} + +func main() { + m := migration{} + migrate.Main(&m) +} diff --git a/repo/fsrepo/migrations/mfsr.go b/repo/fsrepo/migrations/mfsr.go new file mode 100644 index 000000000..21b81f1a3 --- /dev/null +++ b/repo/fsrepo/migrations/mfsr.go @@ -0,0 +1,56 @@ +package mfsr + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path" + "strings" +) + +const VersionFile = "version" + +type RepoPath string + +func (rp RepoPath) VersionFile() string { + return path.Join(string(rp), VersionFile) +} + +func (rp RepoPath) Version() (string, error) { + if rp == "" { + return "", fmt.Errorf("invalid repo path \"%s\"", rp) + } + + fn := rp.VersionFile() + if _, err := os.Stat(fn); os.IsNotExist(err) { + return "", errors.New("no version file in repo at " + string(rp)) + } + + c, err := ioutil.ReadFile(fn) + if err != nil { + return "", err + } + + s := string(c) + s = strings.TrimSpace(s) + return s, nil +} + +func (rp RepoPath) CheckVersion(version string) error { + v, err := rp.Version() + if err != nil { + return err + } + + if v != version { + return fmt.Errorf("versions differ (expected: %s, actual:%s)", version, v) + } + + return nil +} + +func (rp RepoPath) WriteVersion(version string) error { + fn := rp.VersionFile() + return ioutil.WriteFile(fn, []byte(version+"\n"), 0644) +}