Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to call from binary with embedded FS #230

Closed
wants to merge 13 commits into from
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,58 @@ func Down(tx *sql.Tx) error {
}
```

## Embedded migrations

You can embed your migration directory into your binary with a tool like vfsgen and then pass your goose a http.FileSystem
This allows your to handle your migration from with in your binary without depending on external files.

1. Create either SQL or GO goose migration
2. Import `github.com/pressly/goose`
3. Call goose.New with a goose.WithFileSystem() option
4. call Up() method on the struct returned by goose.New()

A [sample Go migration app](./examples/fs-migrations) using [vfsgen](https:/shurcooL/vfsgen) looks like:

```go
package main

import (
"database/sql"
"fmt"
"log"

_ "github.com/lib/pq"

"github.com/pressly/goose"

"github.com/pressly/goose/examples/fs-migrations/postgres"
// import migrations in order to register any .go migrations
_ "github.com/pressly/goose/examples/fs-migrations/postgres/migrations"
)

const (
host = "localhost"
port = 5432
user = "postgres"
password = "postgres"
dbname = "fs_migrate"
)

func main() {
dbConnectionString := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
db, err := sql.Open("postgres", dbConnectionString)
if err != nil {
log.Fatal(err)
}
defer db.Close()

g := goose.New("./postgres/migrations", db, goose.WithFileSystem(postgres.Migrations))
if err := g.Up(); err != nil {
log.Fatal(err)
}
}
```

# Hybrid Versioning
Please, read the [versioning problem](https:/pressly/goose/issues/63#issuecomment-428681694) first.

Expand Down
30 changes: 30 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package goose

import (
"database/sql"
"net/http"
)

type config struct {
dir string
db *sql.DB
lockDB bool
fileSystem http.FileSystem
}



func newConfig(dir string, db *sql.DB, opts ...Option) *config {
cfg := &config{
dir: dir,
db: db,
}
if dir != "" {
cfg.fileSystem = http.Dir(dir)
}
for _, opt := range opts {
opt(cfg)
}
return cfg
}

11 changes: 5 additions & 6 deletions create.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package goose

import (
"database/sql"
"fmt"
"os"
"path/filepath"
Expand All @@ -16,8 +15,8 @@ type tmplVars struct {
CamelName string
}

// Create writes a new blank migration file.
func CreateWithTemplate(db *sql.DB, dir string, tmpl *template.Template, name, migrationType string) error {
// CreateWithTemplate writes a new blank migration file.
func CreateWithTemplate(cfg *config, tmpl *template.Template, name, migrationType string) error {
version := time.Now().Format(timestampFormat)
filename := fmt.Sprintf("%v_%v.%v", version, snakeCase(name), migrationType)

Expand All @@ -29,7 +28,7 @@ func CreateWithTemplate(db *sql.DB, dir string, tmpl *template.Template, name, m
}
}

path := filepath.Join(dir, filename)
path := filepath.Join(cfg.dir, filename)
if _, err := os.Stat(path); !os.IsNotExist(err) {
return errors.Wrap(err, "failed to create migration file")
}
Expand All @@ -53,8 +52,8 @@ func CreateWithTemplate(db *sql.DB, dir string, tmpl *template.Template, name, m
}

// Create writes a new blank migration file.
func Create(db *sql.DB, dir, name, migrationType string) error {
return CreateWithTemplate(db, dir, nil, name, migrationType)
func Create(cfg *config, name, migrationType string) error {
return CreateWithTemplate(cfg, nil, name, migrationType)
}

var sqlMigrationTemplate = template.Must(template.New("goose.sql-migration").Parse(`-- +goose Up
Expand Down
20 changes: 10 additions & 10 deletions dialect.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func SetDialect(d string) error {
case "sqlite3":
dialect = &Sqlite3Dialect{}
case "mssql":
dialect = &SqlServerDialect{}
dialect = &SQLServerDialect{}
case "redshift":
dialect = &RedshiftDialect{}
case "tidb":
Expand Down Expand Up @@ -76,7 +76,7 @@ func (pg PostgresDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
return rows, err
}

func (m PostgresDialect) migrationSQL() string {
func (pg PostgresDialect) migrationSQL() string {
return fmt.Sprintf("SELECT tstamp, is_applied FROM %s WHERE version_id=$1 ORDER BY tstamp DESC LIMIT 1", TableName())
}

Expand Down Expand Up @@ -126,10 +126,10 @@ func (m MySQLDialect) deleteVersionSQL() string {
// MSSQL
////////////////////////////

// SqlServerDialect struct.
type SqlServerDialect struct{}
// SQLServerDialect struct.
type SQLServerDialect struct{}

func (m SqlServerDialect) createVersionTableSQL() string {
func (m SQLServerDialect) createVersionTableSQL() string {
return fmt.Sprintf(`CREATE TABLE %s (
id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
version_id BIGINT NOT NULL,
Expand All @@ -138,11 +138,11 @@ func (m SqlServerDialect) createVersionTableSQL() string {
);`, TableName())
}

func (m SqlServerDialect) insertVersionSQL() string {
func (m SQLServerDialect) insertVersionSQL() string {
return fmt.Sprintf("INSERT INTO %s (version_id, is_applied) VALUES (@p1, @p2);", TableName())
}

func (m SqlServerDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
func (m SQLServerDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
rows, err := db.Query(fmt.Sprintf("SELECT version_id, is_applied FROM %s ORDER BY id DESC", TableName()))
if err != nil {
return nil, err
Expand All @@ -151,7 +151,7 @@ func (m SqlServerDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
return rows, err
}

func (m SqlServerDialect) migrationSQL() string {
func (m SQLServerDialect) migrationSQL() string {
const tpl = `
WITH Migrations AS
(
Expand All @@ -168,7 +168,7 @@ ORDER BY tstamp DESC
return fmt.Sprintf(tpl, TableName())
}

func (m SqlServerDialect) deleteVersionSQL() string {
func (m SQLServerDialect) deleteVersionSQL() string {
return fmt.Sprintf("DELETE FROM %s WHERE version_id=@p1;", TableName())
}

Expand Down Expand Up @@ -239,7 +239,7 @@ func (rs RedshiftDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
return rows, err
}

func (m RedshiftDialect) migrationSQL() string {
func (rs RedshiftDialect) migrationSQL() string {
return fmt.Sprintf("SELECT tstamp, is_applied FROM %s WHERE version_id=$1 ORDER BY tstamp DESC LIMIT 1", TableName())
}

Expand Down
17 changes: 8 additions & 9 deletions down.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package goose

import (
"database/sql"
"fmt"
)

// Down rolls back a single migration from the current version.
func Down(db *sql.DB, dir string) error {
currentVersion, err := GetDBVersion(db)
func Down(cfg *config) error {
currentVersion, err := GetDBVersion(cfg.db)
if err != nil {
return err
}

migrations, err := CollectMigrations(dir, minVersion, maxVersion)
migrations, err := CollectMigrations(cfg, minVersion, maxVersion)
if err != nil {
return err
}
Expand All @@ -22,18 +21,18 @@ func Down(db *sql.DB, dir string) error {
return fmt.Errorf("no migration %v", currentVersion)
}

return current.Down(db)
return current.Down(cfg)
}

// DownTo rolls back migrations to a specific version.
func DownTo(db *sql.DB, dir string, version int64) error {
migrations, err := CollectMigrations(dir, minVersion, maxVersion)
func DownTo(cfg *config, version int64) error {
migrations, err := CollectMigrations(cfg, minVersion, maxVersion)
if err != nil {
return err
}

for {
currentVersion, err := GetDBVersion(db)
currentVersion, err := GetDBVersion(cfg.db)
if err != nil {
return err
}
Expand All @@ -49,7 +48,7 @@ func DownTo(db *sql.DB, dir string, version int64) error {
return nil
}

if err = current.Down(db); err != nil {
if err = current.Down(cfg); err != nil {
return err
}
}
Expand Down
3 changes: 2 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# 1. [SQL migrations](sql-migrations)
# 2. [Go migrations](go-migrations)
# 2. [Go migrations](go-migrations)
# 3. [Go binary migrations](fs-migrations)
23 changes: 23 additions & 0 deletions examples/fs-migrations/assets_generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// +build ignore

package main

import (
"log"

"github.com/shurcooL/vfsgen"

"github.com/pressly/goose/examples/fs-migrations/postgres"
)

func main() {
err := vfsgen.Generate(postgres.Migrations, vfsgen.Options{
PackageName: "postgres",
BuildTags: "!dev",
VariableName: "Migrations",
})

if err != nil {
log.Fatalln(err)
}
}
37 changes: 37 additions & 0 deletions examples/fs-migrations/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

import (
"database/sql"
"fmt"
"log"

_ "github.com/lib/pq"

"github.com/pressly/goose"

"github.com/pressly/goose/examples/fs-migrations/postgres"
// import migrations in order to register any .go migrations
_ "github.com/pressly/goose/examples/fs-migrations/postgres/migrations"
)

const (
host = "localhost"
port = 5432
user = "postgres"
password = "postgres"
dbname = "fs_migrate"
)

func main() {
dbConnectionString := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
db, err := sql.Open("postgres", dbConnectionString)
if err != nil {
log.Fatal(err)
}
defer db.Close()

g := goose.New("./postgres/migrations", db, goose.WithFileSystem(postgres.Migrations))
if err := g.Up(); err != nil {
log.Fatal(err)
}
}
12 changes: 12 additions & 0 deletions examples/fs-migrations/makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
all: build

setup:
go get -u github.com/shurcool/vfsgen

build:
cd postgres && go generate -tags dev && cd -
go build -ldflags '-X main.version=1.0'

clean:
go clean
rm -f postgres/assets_vfsdata.go
1 change: 1 addition & 0 deletions examples/fs-migrations/postgres/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package postgres
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- +goose Up
CREATE TABLE users (
id int NOT NULL PRIMARY KEY,
username text,
name text,
surname text
);

INSERT INTO users VALUES
(0, 'root', '', ''),
(1, 'vojtechvitek', 'Vojtech', 'Vitek');

-- +goose Down
DROP TABLE users;
27 changes: 27 additions & 0 deletions examples/fs-migrations/postgres/migrations/00002_rename_root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package migrations

import (
"database/sql"

"github.com/pressly/goose"
)

func init() {
goose.AddMigration(Up00002, Down00002)
}

func Up00002(tx *sql.Tx) error {
_, err := tx.Exec("UPDATE users SET username='admin' WHERE username='root';")
if err != nil {
return err
}
return nil
}

func Down00002(tx *sql.Tx) error {
_, err := tx.Exec("UPDATE users SET username='root' WHERE username='admin';")
if err != nil {
return err
}
return nil
}
Loading