Skip to content

Commit

Permalink
refactor(cmd): Update command line stuff
Browse files Browse the repository at this point in the history
- Rename package from command to cmd.
- Split up source files by functionality.
- Add dep on github.com/rafaelespinoza/alf.
- Adjust code for alf package.
- Improve usage funcs.
- Update README.
- Remove debug flag, was for figuring out flag parsing but that's mostly
  figured out now.
  • Loading branch information
rafaelespinoza committed May 16, 2021
1 parent 77a911e commit 0c07e70
Show file tree
Hide file tree
Showing 14 changed files with 464 additions and 396 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
[![postgres](https://github.com/rafaelespinoza/godfish/actions/workflows/postgres.yml/badge.svg)](https://github.com/rafaelespinoza/godfish/actions/workflows/postgres.yml)
[![sqlite3](https://github.com/rafaelespinoza/godfish/actions/workflows/sqlite3.yml/badge.svg)](https://github.com/rafaelespinoza/godfish/actions/workflows/sqlite3.yml)

`godfish` is a relational database migration manager, similar to the very
good [`dogfish`](https://github.com/dwb/dogfish), but written in golang.
`godfish` is a database migration manager, similar to the very good
[`dogfish`](https://github.com/dwb/dogfish), but written in golang.

## goals

- use the native query language in the migration files, no other high-level DSLs
- interface with many DBs
- as little dependencies outside of the standard library as possible
- light on dependencies
- not terrible error messages

## build
Expand Down
6 changes: 4 additions & 2 deletions drivers/cassandra/godfish/main.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package main

import (
"context"
"log"
"os"

"github.com/rafaelespinoza/godfish/drivers/cassandra"
"github.com/rafaelespinoza/godfish/internal/commands"
"github.com/rafaelespinoza/godfish/internal/cmd"
)

func main() {
if err := commands.Run(cassandra.NewDriver()); err != nil {
root := cmd.New(cassandra.NewDriver())
if err := root.Run(context.TODO(), os.Args[1:]); err != nil {
log.Println(err)
os.Exit(1)
}
Expand Down
6 changes: 4 additions & 2 deletions drivers/mysql/godfish/main.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package main

import (
"context"
"log"
"os"

"github.com/rafaelespinoza/godfish/drivers/mysql"
"github.com/rafaelespinoza/godfish/internal/commands"
"github.com/rafaelespinoza/godfish/internal/cmd"
)

func main() {
if err := commands.Run(mysql.NewDriver()); err != nil {
root := cmd.New(mysql.NewDriver())
if err := root.Run(context.TODO(), os.Args[1:]); err != nil {
log.Println(err)
os.Exit(1)
}
Expand Down
6 changes: 4 additions & 2 deletions drivers/postgres/godfish/main.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package main

import (
"context"
"log"
"os"

"github.com/rafaelespinoza/godfish/drivers/postgres"
"github.com/rafaelespinoza/godfish/internal/commands"
"github.com/rafaelespinoza/godfish/internal/cmd"
)

func main() {
if err := commands.Run(postgres.NewDriver()); err != nil {
root := cmd.New(postgres.NewDriver())
if err := root.Run(context.TODO(), os.Args[1:]); err != nil {
log.Println(err)
os.Exit(1)
}
Expand Down
6 changes: 4 additions & 2 deletions drivers/sqlite3/godfish/main.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package main

import (
"context"
"log"
"os"

"github.com/rafaelespinoza/godfish/drivers/sqlite3"
"github.com/rafaelespinoza/godfish/internal/commands"
"github.com/rafaelespinoza/godfish/internal/cmd"
)

func main() {
if err := commands.Run(sqlite3.NewDriver()); err != nil {
root := cmd.New(sqlite3.NewDriver())
if err := root.Run(context.TODO(), os.Args[1:]); err != nil {
log.Println(err)
os.Exit(1)
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ require (
github.com/gocql/gocql v0.0.0-20210504150947-558dfae50b5d
github.com/lib/pq v1.10.0
github.com/mattn/go-sqlite3 v1.14.7
github.com/rafaelespinoza/alf v0.2.0
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rafaelespinoza/alf v0.2.0 h1:Ec6XO3omGpneic0GHZB4vBsT/RopzUoca2Vi/Dn+4ZA=
github.com/rafaelespinoza/alf v0.2.0/go.mod h1:e+DkMzlAa4KONZ2CPAxL7jnh67++BDXl9kEdMOef5rM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
Expand Down
124 changes: 124 additions & 0 deletions internal/cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Package cmd contains all the CLI stuff.
package cmd

import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"

"github.com/rafaelespinoza/alf"
"github.com/rafaelespinoza/godfish"
)

var (
// commonArgs are flag inputs for use in any subcommand.
commonArgs struct {
Conf string
Files string
}
// bin is the name of the binary.
bin = os.Args[0]
// theDriver is passed in from a Driver's package main.
theDriver godfish.Driver
)

// Root abstracts a top-level command from package main.
type Root interface {
// Run is the entry point. It should be called with os.Args[1:].
Run(ctx context.Context, args []string) error
}

// New constructs a top-level command with subcommands.
func New(driver godfish.Driver) Root {
theDriver = driver
del := &alf.Delegator{
Description: "main command for " + bin,
Subs: map[string]alf.Directive{
"create-migration": makeCreateMigration("create-migration"),
"info": makeInfo("info"),
"init": makeInit("init"),
"migrate": makeMigrate("migrate"),
"remigrate": makeRemigrate("remigrate"),
"rollback": makeRollback("rollback"),
"version": makeVersion("version"),
},
}

rootFlags := flag.NewFlagSet("godfish", flag.ExitOnError)
rootFlags.Usage = func() {
fmt.Fprintf(rootFlags.Output(), `Usage:
%s [flags] command [sub-flags]
Description:
godfish is a database migration manager. It tracks the status of migrations
by recording a timestamp in a table called "schema_migrations" in the
"migration_id" column. Those timestamps correspond to SQL migration files
that you write and store somewhere on the filesystem. You need to configure
the path to the SQL migration files as well as the name of the driver to use
(ie: postgres, mysql, potato, potato).
Configuration options are set with flags or with a configuration file. Options
specified via flags will take precedence over the config file.
Specify database connection params with environment variable:
DB_DSN=
The following flags should go before the command.
`,
bin)
printFlagDefaults(rootFlags)
fmt.Fprintf(
rootFlags.Output(), `
Commands:
These will have their own set of flags. Put them after the command.
%v
Examples:
%s [command] -h
`,
strings.Join(del.DescribeSubcommands(), "\n\t"), bin)
}

rootFlags.StringVar(&commonArgs.Conf, "conf", ".godfish.json", "path to godfish config file")
rootFlags.StringVar(
&commonArgs.Files,
"files",
"",
"path to migration files, can also set with config file",
)
del.Flags = rootFlags

return &alf.Root{
Delegator: del,
PrePerform: func(_ context.Context) error {
// Look for config file and if present, merge those values with
// input flag values.
var conf godfish.Config
if data, ierr := os.ReadFile(commonArgs.Conf); ierr != nil {
// probably no config file present, rely on arguments instead.
} else if ierr = json.Unmarshal(data, &conf); ierr != nil {
return ierr
}
if commonArgs.Files == "" && conf.PathToFiles != "" {
commonArgs.Files = conf.PathToFiles
}

return nil
},
}
}

// printFlagDefaults calls PrintDefaults on f. It helps make help message
// formatting more consistent.
func printFlagDefaults(f *flag.FlagSet) {
fmt.Printf("\n%s flags:\n\n", f.Name())
f.PrintDefaults()
}
56 changes: 56 additions & 0 deletions internal/cmd/create-migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package cmd

import (
"context"
"flag"
"fmt"

"github.com/rafaelespinoza/alf"
"github.com/rafaelespinoza/godfish"
)

func makeCreateMigration(name string) alf.Directive {
var label string
var reversible bool

return &alf.Command{
Description: "generate migration files",
Setup: func(p flag.FlagSet) *flag.FlagSet {
flags := flag.NewFlagSet(name, flag.ExitOnError)
flags.StringVar(
&label,
"name",
"",
"label the migration, ie: create_foos_table, update_bars_qux",
)
flags.BoolVar(
&reversible,
"reversible",
true,
"create a reversible migration?",
)
flags.Usage = func() {
fmt.Printf(`Usage: %s [godfish-flags] %s [%s-flags]
Generate migration files: one meant for the "forward" direction,
another meant for "reverse". Optionally create a migration in the forward
direction only by passing the flag "-reversible=false". The "name" flag has
no effects other than on the generated filename. The output filename
automatically has a "version". Timestamp format: %s.
`,
bin, name, name, godfish.TimeFormat,
)
printFlagDefaults(&p)
printFlagDefaults(flags)
}
return flags
},
Run: func(_ context.Context) error {
migration, err := godfish.NewMigrationParams(label, reversible, commonArgs.Files)
if err != nil {
return err
}
return migration.GenerateFiles()
},
}
}
71 changes: 71 additions & 0 deletions internal/cmd/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cmd

import (
"context"
"flag"
"fmt"
"strings"

"github.com/rafaelespinoza/alf"
"github.com/rafaelespinoza/godfish"
)

func makeInfo(name string) alf.Directive {
var direction, version string

return &alf.Command{
Description: "output applied migrations, migrations to apply",
Setup: func(p flag.FlagSet) *flag.FlagSet {
flags := flag.NewFlagSet(name, flag.ExitOnError)
flags.StringVar(
&direction,
"direction",
"forward",
"which way to look? (forward|reverse)",
)
flags.StringVar(
&version,
"version",
"",
fmt.Sprintf("timestamp of migration, format: %s", godfish.TimeFormat),
)
flags.Usage = func() {
fmt.Printf(`Usage: %s [godfish-flags] %s [%s-flags]
List applied migrations, preview migrations to apply.
It's an introspection tool that can be used to show exactly which migration
versions would be applied, in either a forward or reverse direction, before
applying them.
Migrations are categorized as:
- Available. Known to godfish, either already applied, or can be applied.
- Applied. They have been migrated against the DB.
- To Apply. They haven't been applied yet, but can be.
It also takes a "direction" flag if you want to know what would be applied
in a rollback or remigrate operation. The "version" flag can be used to
limit or extend the range of migrations to apply.
`,
bin, name, name)
printFlagDefaults(&p)
printFlagDefaults(flags)
}
return flags
},
Run: func(_ context.Context) error {
direction := whichDirection(direction)
return godfish.Info(theDriver, commonArgs.Files, direction, version)
},
}
}

func whichDirection(input string) (direction godfish.Direction) {
direction = godfish.DirForward
d := strings.ToLower(input)
if strings.HasPrefix(d, "rev") || strings.HasPrefix(d, "back") {
direction = godfish.DirReverse
}
return
}
Loading

0 comments on commit 0c07e70

Please sign in to comment.