diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6cae2738..d8b1d476 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,35 +24,6 @@ jobs: uses: actions/checkout@v2 - run: go test -race ./... - cli: - name: CLI - env: - SQLITE3_DATABASE: cli-test.db - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - go: [1.19] - runs-on: ${{ matrix.os }} - steps: - - name: Set up Go 1.x - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.go }} - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - - run: go install ./cmd/rel - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - with: - repository: Fs02/go-todo-backend - path: project - - name: Test CLI - working-directory: project - run: | - rel -v - rel migrate - rel rollback - coverage: name: Coverage env: diff --git a/.goreleaser.yml b/.goreleaser.yml index 18d35b99..a32f4957 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,34 +1,5 @@ -before: - hooks: - - go mod download - - go generate ./... builds: - - main: ./cmd/rel/main.go - env: - - CGO_ENABLED=0 - goos: - - linux - - darwin - - windows - goarch: - - 386 - - amd64 - - arm - - arm64 -archives: - - replacements: - darwin: Darwin - linux: Linux - windows: Windows - 386: i386 - amd64: x86_64 - format_overrides: - - goos: windows - format: zip -checksum: - name_template: 'checksums.txt' -snapshot: - name_template: "{{ .Tag }}-next" + - skip: true changelog: sort: asc filters: @@ -36,30 +7,3 @@ changelog: - '^docs:' - '^test:' - '^chore:' -nfpms: - - file_name_template: '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' - homepage: https://go-rel.github.io - description: Modern Database Access Layer for Golang - maintainer: Muhammad Surya Asriadie - license: MIT - vendor: REL - formats: - - apk - - deb - - rpm - dependencies: - - golang -brews: - - tap: - owner: go-rel - name: homebrew-tap - token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" - commit_author: - name: REL - homepage: "https://go-rel.github.io/" - description: "Database migration using REL" - license: "MIT" - folder: Formula - dependencies: - - name: golang - type: optional diff --git a/cmd/rel/internal/migrate.go b/cmd/rel/internal/migrate.go deleted file mode 100644 index e46b4827..00000000 --- a/cmd/rel/internal/migrate.go +++ /dev/null @@ -1,191 +0,0 @@ -package internal - -import ( - "context" - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "os/exec" - "text/template" - - "github.com/serenize/snaker" -) - -const migrationTemplate = ` -package main - -import ( - "context" - "log" - "strings" - "time" - - _ "{{.Driver}}" - db "{{.Adapter}}" - "github.com/go-rel/rel" - "github.com/go-rel/migration" - - "{{.Package}}" -) - -var ( - shutdowns []func() error -) - -func logger(ctx context.Context, op string, message string) func(err error) { - // no op for rel functions. - if strings.HasPrefix(op, "rel-") { - return func(error) {} - } - - if op == "migrate" || op == "rollback" { - log.Print("Running: ", op, " ", message) - } - - t := time.Now() - return func(err error) { - duration := time.Since(t) - if op == "migrate" || op == "rollback" { - log.Print("=> Done: ", op, " ", message, " in ", duration) - } else if {{.Verbose}} { - log.Print("\t[duration: ", duration, " op: ", op, "] ", message) - } - - if err != nil { - log.Println("\tError: ", op, " ", err) - } - } -} - -func main() { - var ( - ctx = context.Background() - ) - - adapter, err := db.Open("{{.DSN}}") - if err != nil { - log.Fatal(err) - } - - var ( - repo = rel.New(adapter) - m = migration.New(repo) - ) - - log.SetFlags(0) - repo.Instrumentation(logger) - m.Instrumentation(logger) - - {{range .Migrations}} - m.Register({{.Version}}, migrations.Migrate{{.Name}}, migrations.Rollback{{.Name}}) - {{end}} - - {{.Command}} -} -` - -var ( - tempdir = "" - stdout io.Writer = os.Stdout - stderr io.Writer = os.Stderr -) - -// ExecMigrate command. -// assumes args already validated. -func ExecMigrate(ctx context.Context, args []string) error { - var ( - defAdapter, defDriver, defDSN = getDatabaseInfo() - fs = flag.NewFlagSet(args[1], flag.ExitOnError) - command = getMigrateCommand(args[1]) - dir = fs.String("dir", "db/migrations", "Path to directory containing migration files") - module = fs.String("module", getModule(), "Module of the main package") - adapter = fs.String("adapter", defAdapter, "Adapter package") - driver = fs.String("driver", defDriver, "Driver package") - dsn = fs.String("dsn", defDSN, "DSN for database connection") - verbose = fs.Bool("verbose", false, "Show logs from REL") - tmpl = template.Must(template.New("migration").Parse(migrationTemplate)) - ) - - fs.Parse(args[2:]) - - if *adapter == "" || *driver == "" || *dsn == "" { - return fmt.Errorf("rel: missing required parameters:\n\tadapter: %s\n\tdriver: %s\n\tdsn: %s", *adapter, *driver, *dsn) - } - - file, err := ioutil.TempFile(tempdir, "rel-*.go") - check(err) - defer os.Remove(file.Name()) - - migrations, err := scanMigration(*dir) - if err != nil { - return err - } - - err = tmpl.Execute(file, struct { - Package string - Command string - Adapter string - Driver string - DSN string - Migrations []migration - Verbose bool - }{ - Package: *module + "/" + *dir, - Command: command, - Adapter: *adapter, - Driver: *driver, - DSN: *dsn, - Migrations: migrations, - Verbose: *verbose, - }) - check(err) - check(file.Close()) - - cmd := exec.CommandContext(ctx, "go", "run", "-mod=mod", file.Name(), "migrate") - cmd.Stdout = stdout - cmd.Stderr = stderr - return cmd.Run() -} - -type migration struct { - Version string - Name string -} - -func scanMigration(dir string) ([]migration, error) { - files, err := ioutil.ReadDir(dir) - if err != nil { - return nil, errors.New("rel: error accessing read migration directory: " + dir) - } - - mFiles := make([]migration, 0, len(files)) - for _, f := range files { - if f.IsDir() { - continue - } - - result := reMigrationFile.FindStringSubmatch(f.Name()) - if len(result) < 3 { - return nil, errors.New("rel: invalid migration file: " + f.Name()) - } - - mFiles = append(mFiles, migration{ - Version: result[1], - Name: snaker.SnakeToCamel(result[2]), - }) - } - - return mFiles, err -} - -func getMigrateCommand(cmd string) string { - switch cmd { - case "rollback", "down": - return "m.Rollback(ctx)" - default: - return "m.Migrate(ctx)" - } -} diff --git a/cmd/rel/internal/migrate_test.go b/cmd/rel/internal/migrate_test.go deleted file mode 100644 index 3136ea84..00000000 --- a/cmd/rel/internal/migrate_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package internal - -import ( - "bytes" - "context" - "errors" - "os" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestExecMigrate(t *testing.T) { - t.Run("missing required parameters", func(t *testing.T) { - var ( - ctx = context.TODO() - args = []string{ - "rel", - "migrate", - } - ) - - assert.Equal(t, errors.New("rel: missing required parameters:\n\tadapter: \n\tdriver: \n\tdsn: "), ExecMigrate(ctx, args)) - }) - - t.Run("invalid migration dir", func(t *testing.T) { - var ( - ctx = context.TODO() - args = []string{ - "rel", - "migrate", - "-adapter=github.com/go-rel/sqlite3", - "-driver=github.com/mattn/go-sqlite3", - "-dsn=:memory:", - "-dir=db", - } - ) - - assert.Equal(t, errors.New("rel: error accessing read migration directory: db"), ExecMigrate(ctx, args)) - }) - - t.Run("success", func(t *testing.T) { - var ( - ctx = context.TODO() - args = []string{ - "rel", - "migrate", - "-dir=testdata/migrations", - "-module=github.com/go-rel/rel/cmd/rel/internal", - "-adapter=github.com/go-rel/sqlite3", - "-driver=github.com/mattn/go-sqlite3", - "-dsn=:memory:", - "-verbose=false", - } - dir = "testdata" - buff = &bytes.Buffer{} - ) - - tempdir = dir - stderr = buff - defer func() { stderr = os.Stderr }() - - err := ExecMigrate(ctx, args) - assert.Contains(t, buff.String(), "Running: migrate 1 create table todos") - assert.Contains(t, buff.String(), "Done: migrate 1 create table todos") - assert.Nil(t, err) - }) -} - -func TestScanMigration(t *testing.T) { - tests := []struct { - dir string - migrations []migration - err error - }{ - { - dir: "testdata/migrations", - migrations: []migration{ - { - Version: "1", - Name: "CreateSamples", - }, - }, - }, - { - dir: "db", - err: errors.New("rel: error accessing read migration directory: db"), - }, - { - dir: "../", - err: errors.New("rel: invalid migration file: main.go"), - }, - } - - for _, test := range tests { - t.Run(test.dir, func(t *testing.T) { - var ( - migrations, err = scanMigration(test.dir) - ) - - assert.Equal(t, test.err, err) - assert.Equal(t, test.migrations, migrations) - }) - } - -} - -func TestGetMigrateCommand(t *testing.T) { - assert.Equal(t, "m.Rollback(ctx)", getMigrateCommand("rollback")) - assert.Equal(t, "m.Rollback(ctx)", getMigrateCommand("down")) - assert.Equal(t, "m.Migrate(ctx)", getMigrateCommand("migrate")) - assert.Equal(t, "m.Migrate(ctx)", getMigrateCommand("up")) -} diff --git a/cmd/rel/internal/testdata/migrations/1_create_samples.go b/cmd/rel/internal/testdata/migrations/1_create_samples.go deleted file mode 100644 index ca4d1879..00000000 --- a/cmd/rel/internal/testdata/migrations/1_create_samples.go +++ /dev/null @@ -1,15 +0,0 @@ -package migrations - -import "github.com/go-rel/rel" - -// MigrateCreateSamples definition -func MigrateCreateSamples(schema *rel.Schema) { - schema.CreateTable("todos", func(t *rel.Table) { - t.ID("id") - }) -} - -// RollbackCreateSamples definition -func RollbackCreateSamples(schema *rel.Schema) { - schema.DropTable("todos") -} diff --git a/cmd/rel/internal/util.go b/cmd/rel/internal/util.go deleted file mode 100644 index 609c8cdd..00000000 --- a/cmd/rel/internal/util.go +++ /dev/null @@ -1,95 +0,0 @@ -package internal - -import ( - "fmt" - "io/ioutil" - "os" - "regexp" - "strings" -) - -var ( - reMigrationFile = regexp.MustCompile(`^(\d+)_([a-z_]+)\.go$`) - reGomod = regexp.MustCompile(`module\s(\S+)`) - gomod = "go.mod" -) - -func getDatabaseInfo() (string, string, string) { - var adapter, driver, dsn string - switch { - case os.Getenv("DATABASE_URL") != "": - adapter = os.Getenv("DATABASE_ADAPTER") - driver = os.Getenv("DATABASE_DRIVER") - dsn = os.Getenv("DATABASE_URL") - case os.Getenv("SQLITE3_DATABASE") != "": - adapter = "github.com/go-rel/sqlite3" - driver = "github.com/mattn/go-sqlite3" - dsn = os.Getenv("SQLITE3_DATABASE") - case os.Getenv("MYSQL_HOST") != "": - adapter = "github.com/go-rel/mysql" - driver = "github.com/go-sql-driver/mysql" - dsn = fmt.Sprintf("%s:%s@(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", - os.Getenv("MYSQL_USERNAME"), - os.Getenv("MYSQL_PASSWORD"), - os.Getenv("MYSQL_HOST"), - os.Getenv("MYSQL_PORT"), - os.Getenv("MYSQL_DATABASE")) - case os.Getenv("POSTGRES_HOST") != "": - adapter = "github.com/go-rel/postgres" - driver = "github.com/lib/pq" - dsn = fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", - os.Getenv("POSTGRES_USERNAME"), - os.Getenv("POSTGRES_PASSWORD"), - os.Getenv("POSTGRES_HOST"), - os.Getenv("POSTGRES_PORT"), - os.Getenv("POSTGRES_DATABASE")) - case os.Getenv("POSTGRESQL_HOST") != "": - adapter = "github.com/go-rel/postgres" - driver = "github.com/lib/pq" - dsn = fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", - os.Getenv("POSTGRESQL_USERNAME"), - os.Getenv("POSTGRESQL_PASSWORD"), - os.Getenv("POSTGRESQL_HOST"), - os.Getenv("POSTGRESQL_PORT"), - os.Getenv("POSTGRESQL_DATABASE")) - } - - return adapter, driver, dsn -} - -func getModule() string { - if module := getModuleFromGomod(); module != "" { - return module - } - - return getModuleFromGopath() -} - -func getModuleFromGomod() string { - data, err := ioutil.ReadFile(gomod) - if err != nil { - return "" - } - - result := reGomod.FindSubmatch(data) - if len(result) < 2 { - return "" - } - - return string(result[1]) -} - -func getModuleFromGopath() string { - var ( - gopath = os.Getenv("GOPATH") + "/src/" - wd, _ = os.Getwd() - ) - - return strings.TrimPrefix(wd, gopath) -} - -func check(err error) { - if err != nil { - panic(err) - } -} diff --git a/cmd/rel/internal/util_test.go b/cmd/rel/internal/util_test.go deleted file mode 100644 index 150123c7..00000000 --- a/cmd/rel/internal/util_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package internal - -import ( - "errors" - "io/ioutil" - "os" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetDatabaseInfo(t *testing.T) { - t.Run("database", func(t *testing.T) { - os.Setenv("DATABASE_ADAPTER", "github.com/go-rel/mysql") - os.Setenv("DATABASE_DRIVER", "github.com/go-sql-driver/mysql") - os.Setenv("DATABASE_URL", "user:password@(localhost:3306)/db?charset=utf8&parseTime=True&loc=Local") - - defer os.Setenv("DATABASE_ADAPTER", "") - defer os.Setenv("DATABASE_DRIVER", "") - defer os.Setenv("DATABASE_URL", "") - - adapter, driver, url := getDatabaseInfo() - assert.Equal(t, "github.com/go-rel/mysql", adapter) - assert.Equal(t, "github.com/go-sql-driver/mysql", driver) - assert.Equal(t, "user:password@(localhost:3306)/db?charset=utf8&parseTime=True&loc=Local", url) - }) - - t.Run("mysql", func(t *testing.T) { - os.Setenv("MYSQL_HOST", "localhost") - os.Setenv("MYSQL_PORT", "3306") - os.Setenv("MYSQL_DATABASE", "db") - os.Setenv("MYSQL_USERNAME", "user") - os.Setenv("MYSQL_PASSWORD", "password") - - defer os.Setenv("MYSQL_HOST", "") - defer os.Setenv("MYSQL_PORT", "") - defer os.Setenv("MYSQL_DATABASE", "") - defer os.Setenv("MYSQL_USERNAME", "") - defer os.Setenv("MYSQL_PASSWORD", "") - - adapter, driver, url := getDatabaseInfo() - assert.Equal(t, "github.com/go-rel/mysql", adapter) - assert.Equal(t, "github.com/go-sql-driver/mysql", driver) - assert.Equal(t, "user:password@(localhost:3306)/db?charset=utf8&parseTime=True&loc=Local", url) - }) - - t.Run("postgresql", func(t *testing.T) { - os.Setenv("POSTGRES_HOST", "localhost") - os.Setenv("POSTGRES_PORT", "5432") - os.Setenv("POSTGRES_DATABASE", "db") - os.Setenv("POSTGRES_USERNAME", "user") - os.Setenv("POSTGRES_PASSWORD", "password") - - defer os.Setenv("POSTGRES_HOST", "") - defer os.Setenv("POSTGRES_PORT", "") - defer os.Setenv("POSTGRES_DATABASE", "") - defer os.Setenv("POSTGRES_USERNAME", "") - defer os.Setenv("POSTGRES_PASSWORD", "") - - adapter, driver, url := getDatabaseInfo() - assert.Equal(t, "github.com/go-rel/postgres", adapter) - assert.Equal(t, "github.com/lib/pq", driver) - assert.Equal(t, "postgres://user:password@localhost:5432/db?sslmode=disable", url) - }) - - t.Run("postgresql alternative", func(t *testing.T) { - os.Setenv("POSTGRESQL_HOST", "localhost") - os.Setenv("POSTGRESQL_PORT", "5432") - os.Setenv("POSTGRESQL_DATABASE", "db") - os.Setenv("POSTGRESQL_USERNAME", "user") - os.Setenv("POSTGRESQL_PASSWORD", "password") - - defer os.Setenv("POSTGRESQL_HOST", "") - defer os.Setenv("POSTGRESQL_PORT", "") - defer os.Setenv("POSTGRESQL_DATABASE", "") - defer os.Setenv("POSTGRESQL_USERNAME", "") - defer os.Setenv("POSTGRESQL_PASSWORD", "") - - adapter, driver, url := getDatabaseInfo() - assert.Equal(t, "github.com/go-rel/postgres", adapter) - assert.Equal(t, "github.com/lib/pq", driver) - assert.Equal(t, "postgres://user:password@localhost:5432/db?sslmode=disable", url) - }) - - t.Run("sqlite3", func(t *testing.T) { - os.Setenv("SQLITE3_DATABASE", "test.db") - defer os.Setenv("SQLITE3_DATABASE", "") - - adapter, driver, url := getDatabaseInfo() - assert.Equal(t, "github.com/go-rel/sqlite3", adapter) - assert.Equal(t, "github.com/mattn/go-sqlite3", driver) - assert.Equal(t, "test.db", url) - }) - -} - -func TestGetModule(t *testing.T) { - t.Run("gomod", func(t *testing.T) { - var ( - file, _ = ioutil.TempFile(os.TempDir(), "go.mod") - ) - - defer os.Remove(file.Name()) - file.WriteString("module github.com/Fs02/go-todo-backend") - file.Close() - - gomod = file.Name() - assert.Equal(t, "github.com/Fs02/go-todo-backend", getModule()) - }) - - t.Run("gomod invalid", func(t *testing.T) { - var ( - file, _ = ioutil.TempFile(os.TempDir(), "go.mod") - ) - - defer os.Remove(file.Name()) - file.WriteString("pkg github.com/Fs02/go-todo-backend") - file.Close() - - gomod = file.Name() - assert.NotEqual(t, "github.com/Fs02/go-todo-backend", getModule()) - }) - - t.Run("gopath", func(t *testing.T) { - assert.NotEmpty(t, getModule()) - }) -} - -func TestInternal(t *testing.T) { - assert.Panics(t, func() { check(errors.New("err")) }) - assert.NotPanics(t, func() { check(nil) }) -} diff --git a/cmd/rel/main.go b/cmd/rel/main.go deleted file mode 100644 index 45c1b9e0..00000000 --- a/cmd/rel/main.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "log" - "os" - - "github.com/go-rel/rel/cmd/rel/internal" - "github.com/subosito/gotenv" -) - -var ( - version = "" -) - -func main() { - log.SetFlags(0) - gotenv.Load() - - var ( - err error - ctx = context.Background() - ) - - if len(os.Args) < 2 { - fmt.Println("Available command are: migrate, rollback") - os.Exit(1) - } - - switch os.Args[1] { - case "migrate", "up", "rollback", "down": - err = internal.ExecMigrate(ctx, os.Args) - case "version", "-v", "-version": - fmt.Println("REL CLI " + version) - case "-help": - fmt.Println("Usage: rel [command] -help") - fmt.Println("Available commands: migrate, rollback") - default: - flag.PrintDefaults() - os.Exit(1) - } - - if err != nil { - log.Fatal(err) - } -}