feat(golang-migrate/migrate): add provider

This commit is contained in:
Kevin Franklin Kim 2024-12-04 10:00:57 +01:00
parent cb17a4c194
commit 1dbfe817a1
No known key found for this signature in database
6 changed files with 330 additions and 5 deletions

5
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/cloudrecipes/packagejson v1.0.0
github.com/digitalocean/godo v1.131.0
github.com/foomo/posh v0.8.2
github.com/golang-migrate/migrate/v4 v4.18.1
github.com/google/go-github/v47 v47.1.0
github.com/joho/godotenv v1.5.1
github.com/pkg/errors v0.9.1
@ -40,7 +41,9 @@ require (
github.com/google/go-querystring v1.1.0 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect
@ -73,7 +76,7 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/term v0.26.0 // indirect
golang.org/x/text v0.20.0 // indirect

15
go.sum
View File

@ -53,6 +53,8 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
@ -70,10 +72,15 @@ github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@ -145,8 +152,8 @@ github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaK
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
@ -216,8 +223,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=

View File

@ -0,0 +1,52 @@
# POSH Golang Migrate provider
## Usage
### Plugin
```go
package main
import (
// ...
// import database drivers
_ "github.com/golang-migrate/migrate/v4/database/pgx/v5"
// import source drivers
_ "github.com/golang-migrate/migrate/v4/source/file"
// ...
)
type Plugin struct {
l log.Logger
}
func New(l log.Logger) (plugin.Plugin, error) {
var err error
inst := &Plugin{
l: l,
commands: command.Commands{},
}
// ...
inst.commands.Add(migrate.NewCommand(l))
// ...
return inst, nil
}
```
### Config
```yaml
## Migrate
migrate:
# https://github.com/golang-migrate/migrate/blob/master/README.md#migration-sources
databases:
local: pgx5://postgres:postgres@localhost:5432/admin?sslmode=disable
# https://github.com/golang-migrate/migrate/blob/master/README.md#migration-sources
sources:
default: file://migrations
```

View File

@ -0,0 +1,214 @@
package migrate
import (
"context"
"strconv"
"github.com/foomo/posh/pkg/command/tree"
"github.com/foomo/posh/pkg/log"
"github.com/foomo/posh/pkg/prompt/goprompt"
"github.com/foomo/posh/pkg/readline"
"github.com/foomo/posh/pkg/util/suggests"
"github.com/golang-migrate/migrate/v4"
"github.com/pkg/errors"
"github.com/spf13/viper"
)
type (
Command struct {
l log.Logger
name string
config Config
configKey string
commandTree tree.Root
}
CommandOption func(*Command)
)
// ------------------------------------------------------------------------------------------------
// ~ Options
// ------------------------------------------------------------------------------------------------
func CommandWithName(v string) CommandOption {
return func(o *Command) {
o.name = v
}
}
func CommandWithConfigKey(v string) CommandOption {
return func(o *Command) {
o.configKey = v
}
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewCommand(l log.Logger, opts ...CommandOption) (*Command, error) {
inst := &Command{
l: l.Named("migrate"),
name: "migrate",
configKey: "migrate",
}
for _, opt := range opts {
if opt != nil {
opt(inst)
}
}
if err := viper.UnmarshalKey(inst.configKey, &inst.config); err != nil {
return nil, err
}
inst.commandTree = tree.New(&tree.Node{
Name: inst.name,
Description: "Manage database migrations",
Nodes: tree.Nodes{
{
Name: "database",
Values: func(ctx context.Context, r *readline.Readline) []goprompt.Suggest {
return suggests.List(inst.config.Databases())
},
Nodes: tree.Nodes{
{
Name: "source",
Values: func(ctx context.Context, r *readline.Readline) []goprompt.Suggest {
return suggests.List(inst.config.Sources())
},
Nodes: tree.Nodes{
{
Name: "up",
Description: "Migrate the DB to the most recent version available",
Execute: inst.execute,
},
{
Name: "up-by-one",
Description: "Migrate the DB up by 1",
Execute: inst.execute,
},
{
Name: "down",
Description: "Roll back the version by 1",
Execute: inst.execute,
},
{
Name: "down-by-one",
Description: "Migrate the DB down by 1",
Execute: inst.execute,
},
{
Name: "force",
Description: "Sets a migration version",
Args: tree.Args{
{
Name: "version",
Description: "Version to migrate",
},
},
Execute: inst.execute,
},
{
Name: "version",
Description: "Print the current version of the database",
Execute: inst.execute,
},
{
Name: "drop",
Description: "Deletes everything in the database",
Execute: inst.execute,
},
},
},
},
},
},
})
return inst, nil
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (c *Command) Name() string {
return c.commandTree.Node().Name
}
func (c *Command) Description() string {
return c.commandTree.Node().Description
}
func (c *Command) Complete(ctx context.Context, r *readline.Readline) []goprompt.Suggest {
return c.commandTree.Complete(ctx, r)
}
func (c *Command) Execute(ctx context.Context, r *readline.Readline) error {
return c.commandTree.Execute(ctx, r)
}
func (c *Command) Help(ctx context.Context, r *readline.Readline) string {
return c.commandTree.Help(ctx, r)
}
// ------------------------------------------------------------------------------------------------
// ~ Private methods
// ------------------------------------------------------------------------------------------------
func (c *Command) execute(ctx context.Context, r *readline.Readline) error {
database := c.config.Database(r.Args().At(0))
source := c.config.Source(r.Args().At(1))
m, err := migrate.New(source, database)
if err != nil {
return err
}
m.Log = &logger{l: c.l}
go func() {
select {
case <-ctx.Done():
c.l.Info("triggering graceful migration shutdown")
m.GracefulStop <- true
}
}()
defer func() {
if dErr, sErr := m.Close(); dErr != nil {
c.l.Warn(dErr)
} else if sErr != nil {
c.l.Warn(sErr)
}
}()
switch r.Args().At(2) {
case "up":
return m.Up()
case "up-by-one":
return m.Steps(1)
case "down":
return m.Down()
case "down-by-one":
return m.Steps(-1)
case "force":
i, err := strconv.ParseInt(r.Args().At(3), 10, 64)
if err != nil {
return err
}
return m.Force(int(i))
case "drop":
return m.Drop()
case "version":
version, dirty, err := m.Version()
if err != nil {
return err
}
c.l.Infof("Version: %d, Dirty: %t", version, dirty)
return nil
default:
return errors.Errorf("unkown command: %s", r.Args().At(2))
}
}

View File

@ -0,0 +1,32 @@
package migrate
import (
"github.com/samber/lo"
)
type Config struct {
SourcesMap map[string]string `json:"sources" yaml:"sources" mapstructure:"sources"`
DatabasesMap map[string]string `json:"databases" yaml:"databases" mapstructure:"databases"`
}
func (c Config) Sources() []string {
return lo.Keys(c.SourcesMap)
}
func (c Config) Source(name string) string {
if value, ok := c.SourcesMap[name]; ok {
return value
}
return ""
}
func (c Config) Databases() []string {
return lo.Keys(c.DatabasesMap)
}
func (c Config) Database(name string) string {
if value, ok := c.DatabasesMap[name]; ok {
return value
}
return ""
}

View File

@ -0,0 +1,17 @@
package migrate
import (
"github.com/foomo/posh/pkg/log"
)
type logger struct {
l log.Logger
}
func (l *logger) Printf(format string, v ...interface{}) {
l.l.Infof(format, v...)
}
func (l *logger) Verbose() bool {
return l.l.IsLevel(log.LevelDebug)
}