Explorar el Código

Goose CLI implemented as 'kurzd migrate [command]'

Frederic G. MARAND hace 5 años
padre
commit
cacbb5da50

+ 14 - 0
Makefile

@@ -0,0 +1,14 @@
+.PHONY: clean
+
+all: build
+
+build: cmd/kurz/kurz cmd/kurzd/kurzd
+
+clean:
+	rm -f cmd/kurz/kurz cmd/kurzd/kurzd
+
+cmd/kurz/kurz: $(wildcard cmd/kurz/*.go)
+	cd cmd/kurz && go build
+
+cmd/kurzd/kurzd: $(wildcard cmd/kurzd/*.go) $(wildcard migrations/*.go)
+	cd cmd/kurzd && go build

+ 4 - 4
README.md

@@ -21,10 +21,6 @@ _Kurz_ is yet another URL shortener.
     go get code.osinet.fr/fgm/kurz/cmd/kurz
     ```
 1. Configure a MySQL database for Kurz, say `osinet_kurz` 
-1. Load the Kurz schema to the database:
-    ```bash
-    mysql -u<user> -p<password> osinet_kurz -e "source data/schema.sql;"
-    ```
 1. Create the Kurz configuration
     ```bash
     mkdir ~/.kurz
@@ -35,6 +31,10 @@ _Kurz_ is yet another URL shortener.
     ```bash
     kurzd help
     ```
+1. Load the Kurz schema to the database:
+    ```bash
+    kurzd migrate up
+    ```
 
 
 # Runnning the Kurz server

+ 2 - 0
cmd/kurzd/kurzd.go

@@ -25,6 +25,8 @@ kurzd
 	export							Export kurzd data.
 		config						    Export kurz configuration.
 		content							Export kurz content.
+	migrate							Goose migration commands
+		<goose commands>				See https://github.com/pressly/goose
 	uninstall						Uninstall kurzd completely.
 		config							Uninstall the curz configuration file from ~.
 		schema							Uninstall the curz database schema.

+ 41 - 0
cmd/kurzd/migrate.go

@@ -0,0 +1,41 @@
+package main
+
+import (
+	_ "code.osinet.fr/fgm/kurz/migrations"
+	"errors"
+	"github.com/spf13/cobra"
+	"strconv"
+)
+
+var cmdMigrate = &cobra.Command{
+	Args:  cobra.NoArgs,
+	Long:  "Custom Kurz version of Goose CLI",
+	Short: "Top-level command for migrations. Kurz version of 'goose', without the 'create' subcommand.",
+	Use:   "migrate",
+}
+
+// Used by the down-to and up-to commands.
+var migrateTargetVersion int64 = 0
+
+func init() {
+	cmd.AddCommand(cmdMigrate)
+}
+
+func migrateVersionValidator(cmd *cobra.Command, args []string) error {
+	if len(args) != 1 {
+		return errors.New(cmd.Name() + " only accepts a single numeric version parameter")
+	}
+
+	// Arguments count was validated by cmdMigrate.Args: ExactArgs(1).
+	target, err := strconv.ParseInt(args[0], 10, 64)
+	if err != nil {
+		return errors.New(cmd.Name() + " could not parse target version number")
+	}
+
+	if target < 0 {
+		return errors.New(cmd.Name() + " version numbers must be positive")
+	}
+
+	migrateTargetVersion = target
+	return nil
+}

+ 39 - 0
cmd/kurzd/migrate_down.go

@@ -0,0 +1,39 @@
+package main
+
+import (
+	"os"
+
+	_ "code.osinet.fr/fgm/kurz/migrations"
+	"github.com/pressly/goose"
+	"github.com/spf13/cobra"
+)
+
+var cmdMigrateDown = &cobra.Command{
+	Args:  cobra.NoArgs,
+	Long:  "Roll back the latest applied Kurz schema migration. Kurz version of 'goose down'.",
+	Run:   migrateDownHandler,
+	Short: "Roll back latest migration",
+	Use:   "down",
+}
+
+func init() {
+	cmdMigrate.AddCommand(cmdMigrateDown)
+}
+
+// migrateStatusHandler implements the "kurzd migrate down" command.
+func migrateDownHandler(_ *cobra.Command, args []string) {
+	dbDriver, dbDsn := parseDbCred()
+	db, err := dbDial(dbDriver, dbDsn)
+	if err != nil {
+		panic(err)
+	}
+	defer db.Close()
+
+	cwd, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+
+	goose.SetDialect(dbDriver)
+	goose.Down(db, cwd)
+}

+ 39 - 0
cmd/kurzd/migrate_downto.go

@@ -0,0 +1,39 @@
+package main
+
+import (
+	"os"
+
+	_ "code.osinet.fr/fgm/kurz/migrations"
+	"github.com/pressly/goose"
+	"github.com/spf13/cobra"
+)
+
+var cmdMigrateDownTo = &cobra.Command{
+	Args:  migrateVersionValidator,
+	Long:  "Roll the Kurz schema back to a specific version. Kurz version of 'goose down-to'.",
+	Run:   migrateDownToHandler,
+	Short: "Roll back to specific version",
+	Use:   "down-to <version>",
+}
+
+func init() {
+	cmdMigrate.AddCommand(cmdMigrateDownTo)
+}
+
+// migrateStatusHandler implements the "kurzd migrate down" command.
+func migrateDownToHandler(cmd *cobra.Command, args []string) {
+	dbDriver, dbDsn := parseDbCred()
+	db, err := dbDial(dbDriver, dbDsn)
+	if err != nil {
+		panic(err)
+	}
+	defer db.Close()
+
+	cwd, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+
+	goose.SetDialect(dbDriver)
+	goose.DownTo(db, cwd, migrateTargetVersion)
+}

+ 39 - 0
cmd/kurzd/migrate_redo.go

@@ -0,0 +1,39 @@
+package main
+
+import (
+	"os"
+
+	_ "code.osinet.fr/fgm/kurz/migrations"
+	"github.com/pressly/goose"
+	"github.com/spf13/cobra"
+)
+
+var cmdMigrateRedo = &cobra.Command{
+	Args:  cobra.NoArgs,
+	Long:  "Migrate the Kurz schema to the most recent version available. Kurz version of 'goose redo'.",
+	Run:   migrateRedoHandler,
+	Short: "Migrate to latest version",
+	Use:   "redo",
+}
+
+func init() {
+	cmdMigrate.AddCommand(cmdMigrateRedo)
+}
+
+// migrateStatusHandler implements the "kurzd migrate down" command.
+func migrateRedoHandler(_ *cobra.Command, args []string) {
+	dbDriver, dbDsn := parseDbCred()
+	db, err := dbDial(dbDriver, dbDsn)
+	if err != nil {
+		panic(err)
+	}
+	defer db.Close()
+
+	cwd, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+
+	goose.SetDialect(dbDriver)
+	goose.Redo(db, cwd)
+}

+ 39 - 0
cmd/kurzd/migrate_status.go

@@ -0,0 +1,39 @@
+package main
+
+import (
+	"os"
+
+	_ "code.osinet.fr/fgm/kurz/migrations"
+	"github.com/pressly/goose"
+	"github.com/spf13/cobra"
+)
+
+var cmdMigrateStatus = &cobra.Command{
+	Args:  cobra.NoArgs,
+	Long:  "Dump the Kurz migration status table. Kurz version of 'goose status'",
+	Run:   migrateStatusHandler,
+	Short: "Migration status",
+	Use:   "status",
+}
+
+func init() {
+	cmdMigrate.AddCommand(cmdMigrateStatus)
+}
+
+// migrateStatusHandler implements the "kurzd migrate status" command.
+func migrateStatusHandler(_ *cobra.Command, args []string) {
+	dbDriver, dbDsn := parseDbCred()
+	db, err := dbDial(dbDriver, dbDsn)
+	if err != nil {
+		panic(err)
+	}
+	defer db.Close()
+
+	cwd, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+
+	goose.SetDialect(dbDriver)
+	goose.Status(db, cwd)
+}

+ 39 - 0
cmd/kurzd/migrate_up.go

@@ -0,0 +1,39 @@
+package main
+
+import (
+	"os"
+
+	_ "code.osinet.fr/fgm/kurz/migrations"
+	"github.com/pressly/goose"
+	"github.com/spf13/cobra"
+)
+
+var cmdMigrateUp = &cobra.Command{
+	Args:  cobra.NoArgs,
+	Long:  "Migrate the Kurz schema to the most recent version available. Kurz version of 'goose up'.",
+	Run:   migrateUpHandler,
+	Short: "Migrate to latest version",
+	Use:   "up",
+}
+
+func init() {
+	cmdMigrate.AddCommand(cmdMigrateUp)
+}
+
+// migrateStatusHandler implements the "kurzd migrate down" command.
+func migrateUpHandler(_ *cobra.Command, args []string) {
+	dbDriver, dbDsn := parseDbCred()
+	db, err := dbDial(dbDriver, dbDsn)
+	if err != nil {
+		panic(err)
+	}
+	defer db.Close()
+
+	cwd, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+
+	goose.SetDialect(dbDriver)
+	goose.Up(db, cwd)
+}

+ 39 - 0
cmd/kurzd/migrate_upto.go

@@ -0,0 +1,39 @@
+package main
+
+import (
+	"os"
+
+	_ "code.osinet.fr/fgm/kurz/migrations"
+	"github.com/pressly/goose"
+	"github.com/spf13/cobra"
+)
+
+var cmdMigrateUpTo = &cobra.Command{
+	Args:  migrateVersionValidator,
+	Long:  "Migrate the Kurz schema to the most recent version available. Kurz version of 'goose up'.",
+	Run:   migrateUpToHandler,
+	Short: "Migrate to latest version",
+	Use:   "up-to <version>",
+}
+
+func init() {
+	cmdMigrate.AddCommand(cmdMigrateUpTo)
+}
+
+// migrateStatusHandler implements the "kurzd migrate down" command.
+func migrateUpToHandler(cmd *cobra.Command, args []string) {
+	dbDriver, dbDsn := parseDbCred()
+	db, err := dbDial(dbDriver, dbDsn)
+	if err != nil {
+		panic(err)
+	}
+	defer db.Close()
+
+	cwd, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+
+	goose.SetDialect(dbDriver)
+	goose.UpTo(db, cwd, migrateTargetVersion)
+}

+ 39 - 0
cmd/kurzd/migrate_version.go

@@ -0,0 +1,39 @@
+package main
+
+import (
+	"os"
+
+	_ "code.osinet.fr/fgm/kurz/migrations"
+	"github.com/pressly/goose"
+	"github.com/spf13/cobra"
+)
+
+var cmdMigrateVersion = &cobra.Command{
+	Args:  cobra.NoArgs,
+	Long:  "Kurz version of goose version",
+	Run:   migrateVersionHandler,
+	Short: "Migration version",
+	Use:   "version",
+}
+
+func init() {
+	cmdMigrate.AddCommand(cmdMigrateVersion)
+}
+
+// migrateStatusHandler implements the "kurzd migrate status" command.
+func migrateVersionHandler(_ *cobra.Command, args []string) {
+	dbDriver, dbDsn := parseDbCred()
+	db, err := dbDial(dbDriver, dbDsn)
+	if err != nil {
+		panic(err)
+	}
+	defer db.Close()
+
+	cwd, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+
+	goose.SetDialect(dbDriver)
+	goose.Version(db, cwd)
+}

+ 1 - 1
cmd/kurzd/rootCmd.go

@@ -11,7 +11,7 @@ import (
 var cmd = &cobra.Command{
 	Use:   "kurzd",
 	Short: "kurzd is the Kurz daemon and back-office command",
-	Long:  `kurzd is the actual engine for Kurz. Use it as the server for your Kurz clients.
+	Long: `kurzd is the actual engine for Kurz. Use it as the server for your Kurz clients.
 Configure it by copying dist.config.yml to ~/.kurz/config.yml and editing the copy.`,
 }
 var verbose bool

+ 5 - 1
go.mod

@@ -1,11 +1,15 @@
 module code.osinet.fr/fgm/kurz
 
 require (
+	github.com/BurntSushi/toml v0.3.1 // indirect
 	github.com/go-sql-driver/mysql v1.4.0
-	github.com/mitchellh/go-homedir v1.0.0 // indirect
+	github.com/inconshreveable/mousetrap v1.0.0 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/pressly/goose v2.4.3+incompatible
 	github.com/spf13/cobra v0.0.3
 	github.com/spf13/pflag v1.0.3 // indirect
 	github.com/spf13/viper v1.2.1
+	github.com/stretchr/testify v1.2.2 // indirect
 	google.golang.org/appengine v1.3.0 // indirect
 	gopkg.in/yaml.v2 v2.2.1
 )

+ 12 - 2
go.sum

@@ -1,3 +1,6 @@
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -6,14 +9,18 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
-github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I=
 github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pressly/goose v2.4.3+incompatible h1:RR8yPyKaX4WJiVA4Le4AvGhC3g8F1lJWWCbkqQ3Q9bA=
+github.com/pressly/goose v2.4.3+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8=
 github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
@@ -27,6 +34,8 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/viper v1.2.1 h1:bIcUwXqLseLF3BDAZduuNfekWG87ibtFxi59Bq+oI9M=
 github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI=
+github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg=
 golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -34,6 +43,7 @@ golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
 google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

+ 37 - 0
migrations/20181117121947_legacy_schema.go

@@ -0,0 +1,37 @@
+package migrations
+
+import (
+	"database/sql"
+
+	"github.com/pressly/goose"
+)
+
+func init() {
+	goose.AddMigration(Up20181117121947, Down20181117121947)
+}
+
+func Up20181117121947(tx *sql.Tx) error {
+	return simpleRun(tx, strings{
+		`
+DROP TABLE IF EXISTS map;
+`, `
+CREATE TABLE map (
+	hash bigint(20) UNSIGNED NOT NULL DEFAULT 0,
+	url varchar(250) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
+	date1 datetime NOT NULL DEFAULT '1970-01-01 00:00:00',
+	date2 datetime NOT NULL DEFAULT '1970-01-01 00:00:00',
+	date3 datetime NOT NULL DEFAULT '1970-01-01 00:00:00',
+	refcount int(11) NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+`, `
+ALTER TABLE map
+ADD PRIMARY KEY (hash);
+`,
+	})
+}
+
+func Down20181117121947(tx *sql.Tx) error {
+	return simpleRun(tx, strings{`
+DROP TABLE IF EXISTS map;
+`})
+}

+ 19 - 0
migrations/migrations.go

@@ -0,0 +1,19 @@
+package migrations
+
+import "database/sql"
+
+type strings []string
+
+/**
+ * simpleRun is a trivial wrapper executing SQL instructions without a result in sequence.
+ */
+func simpleRun(tx *sql.Tx, instructions strings) error {
+	for _, instruction := range instructions {
+		_, err := tx.Exec(instruction)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}