如何使用Golang可执行文件绑定迁移

I have a Golang project using goose for Mysql migrations. I would like to bind the migrations to the package executable so that the executable can be deployed and used independently from any system, similar to JAR files in JAVA projects.

Is there an equivalent in Golang to accomplish that?

How to get a single file which can migrate database and work

  1. Install

    go get -u github.com/pressly/goose/cmd/goose
    
  2. Make app. I base it on examplemain.go and add run option. Suppose your project is located at github.com/user/project:

    package main
    
    import (
        "database/sql"
        "flag"
        "log"
        "os"
    
        "github.com/pressly/goose"
    
        // Init DB drivers. -- here I recommend remove unnecessary - but it's up to you
        _ "github.com/go-sql-driver/mysql"
        _ "github.com/lib/pq"
        _ "github.com/mattn/go-sqlite3"
        _ "github.com/ziutek/mymysql/godrv"
    
        // here our migrations will live  -- use your path 
        _ "github.com/user/project/migrations"
    )
    
    var (
        flags = flag.NewFlagSet("goose", flag.ExitOnError)
        dir   = flags.String("dir", ".", "directory with migration files")
    )
    
    func main() {
        flags.Usage = usage
        flags.Parse(os.Args[1:])
    
        args := flags.Args()
    
        //////
        if len(args) > 1 && args[0] == "run" {
           log.Printf("PROGRAM RUN
    ")  //
           ..... 
           os.Exit(0)
        }
    
    
        if len(args) > 1 && args[0] == "create" {
            if err := goose.Run("create", nil, *dir, args[1:]...); err != nil {
                log.Fatalf("goose run: %v", err)
            }
            return
        }
    
        if len(args) < 3 {
            flags.Usage()
            return
        }
    
        if args[0] == "-h" || args[0] == "--help" {
            flags.Usage()
            return
        }
    
        driver, dbstring, command := args[0], args[1], args[2]
    
        switch driver {
        case "postgres", "mysql", "sqlite3", "redshift":
            if err := goose.SetDialect(driver); err != nil {
                log.Fatal(err)
            }
        default:
            log.Fatalf("%q driver not supported
    ", driver)
        }
    
        switch dbstring {
        case "":
            log.Fatalf("-dbstring=%q not supported
    ", dbstring)
        default:
        }
    
        if driver == "redshift" {
            driver = "postgres"
        }
    
        db, err := sql.Open(driver, dbstring)
        if err != nil {
            log.Fatalf("-dbstring=%q: %v
    ", dbstring, err)
        }
    
        arguments := []string{}
        if len(args) > 3 {
            arguments = append(arguments, args[3:]...)
        }
    
        if err := goose.Run(command, db, *dir, arguments...); err != nil {
            log.Fatalf("goose run: %v", err)
        }
    }
    
    func usage() {
        log.Print(usagePrefix)
        flags.PrintDefaults()
        log.Print(usageCommands)
    }
    
    var (
        usagePrefix = `Usage: goose [OPTIONS] DRIVER DBSTRING COMMAND
    Drivers:
        postgres
        mysql
        sqlite3
        redshift
    Examples:
        goose sqlite3 ./foo.db status
        goose sqlite3 ./foo.db create init sql
        goose sqlite3 ./foo.db create add_some_column sql
        goose sqlite3 ./foo.db create fetch_user_data go
        goose sqlite3 ./foo.db up
        goose postgres "user=postgres dbname=postgres sslmode=disable" status
        goose mysql "user:password@/dbname?parseTime=true" status
        goose redshift "postgres://user:password@qwerty.us-east-1.redshift.amazonaws.com:5439/db"
    status
    Options:
    `
    
        usageCommands = `
    Commands:
        up                   Migrate the DB to the most recent version available
        up-to VERSION        Migrate the DB to a specific VERSION
        down                 Roll back the version by 1
        down-to VERSION      Roll back to a specific VERSION
        redo                 Re-run the latest migration
        status               Dump the migration status for the current DB
        version              Print the current version of the database
        create NAME [sql|go] Creates new migration file with next version
    `
    )
    
  3. Create folder for migrations:

    mkdir migrations && cd migrations
    
  4. Create first migrations. We will use go-style migrations:

    goose mysql "user:password@/dbname?parseTime=true" create init go
    

    You'll get a file 00001_init.go with Go code. Migrations are baked in it as SQL-commands. Just edit them as you need.

  5. Then go to the main folder and build the application:

    cd ..
    go build -v -o myapp *.go
    
  6. You'll get a file myapp with all the migrations baked in it. To check move it to some other place, for example to /tmp folder, and run from there:

    ./myapp mysql "user:password@/dbname?parseTime=true" status
    
  7. Run your app:

    ./myapp run
    

Result

You have single file which can be used as a migration tool so as a working application itself. All the migration are buil-it. In source code they are stored in a subpackage migrations - so it's easy to edit.

If you use Docker you may put folder to the image.

If not projects like https://github.com/rakyll/statik or https://github.com/jteeuwen/go-bindata can help.

If you're already using Goose, one option would be to write the migrations in Go instead of SQL. Based on the Go migrations example in the Goose repo, when you build the goose binary here, it will bundle all the *.go ones into the binary.

This was the output after I built the example and removed all files except the binary itself. The Go-based migration was embedded:

2017/10/31 11:22:31     Applied At                  Migration
2017/10/31 11:22:31     =======================================
2017/10/31 11:22:31     Mon Jun 19 21:56:00 2017 -- 00002_rename_root.go

If you're looking to use SQL-based migrations but don't want to take on additional dependencies, you could embed your SQL-based migrations into *.go files as string constants, then, starting from the go-migrations example, add an init phase to main.go that writes them out to the current directory before proceeding.