From 57270501ecdf31b9728ffebaa85a7b26e1289e21 Mon Sep 17 00:00:00 2001 From: Sergey Gladkovskiy Date: Wed, 17 Nov 2021 09:46:51 +0300 Subject: [PATCH] - Context data pass was added - Seeding command was added - Some small refactoring of migrate command and usage functions implementation in init function (sic!). --- main.go | 36 ++++++++++++--- migrate.go | 44 +++++++++---------- seed.go | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 29 deletions(-) create mode 100644 seed.go diff --git a/main.go b/main.go index 843211d..5f83b27 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,37 @@ package commands +import ( + "errors" +) + type CommandContextKey string const ( - CommandContextCfgKeyOverall CommandContextKey = "cfg" - CommandContextCfgKeyDB = CommandContextCfgKeyOverall + ".db" - CommandContextCfgKeyLog = CommandContextCfgKeyOverall + ".log" - CommandContextCfgKeyAppInfo = CommandContextCfgKeyOverall + ".appInfo" - CommandContextCfgKeyStage = CommandContextCfgKeyOverall + ".stage" + CommandContextCfgKey CommandContextKey = "cfg" + CommandContextCfgKeyDB = CommandContextCfgKey + ".db" + CommandContextCfgKeyLog = CommandContextCfgKey + ".log" + CommandContextCfgKeyAppInfo = CommandContextCfgKey + ".appInfo" + CommandContextCfgKeyStage = CommandContextCfgKey + ".stage" +) + +const ( + CommandContextObjectKey CommandContextKey = "obj" + CommandContextObjectKeySeeder = CommandContextObjectKey + ".seeder" + CommandContextObjectKeyConfig = CommandContextObjectKey + ".config" ) + +var ( + ErrNoMethodFound = errors.New("seed method is not exists") + ErrSeedIsDisabled = errors.New("seed method is disabled in config") + ErrBadContextValue = errors.New("context value is empty or has wrong type") +) + +// nolint: gochecknoinits // 🤷 +func init() { + MigrateCmd.SetUsageFunc(migrateUsage) + SeedCmd.SetUsageFunc(seedUsage) + + SeedCmd.AddCommand(seedRunCmd) + SeedCmd.AddCommand(seedRunAllCmd) + SeedCmd.AddCommand(seedListCmd) +} diff --git a/migrate.go b/migrate.go index aa33bde..f1a1732 100644 --- a/migrate.go +++ b/migrate.go @@ -3,7 +3,6 @@ package commands import ( "context" "database/sql" - "errors" "fmt" "os" @@ -18,25 +17,21 @@ import ( ) const ( - failureCode = 1 - errStrFormat = "%s %s error: %w" + CmdFailureCode = 1 + CmdErrStrFormat = "%s %s error: %w" ) -var ( - // MigrateCmd is a github.com/pressly/goose database migrate wrapper command. - MigrateCmd = &cobra.Command{ - Use: "migrate", - Short: "Database migrations command", - ValidArgs: []string{"up", "up-by-one", "up-to", "create", "down", "down-to", "fix", "redo", "reset", "status", "version"}, - Args: cobra.MinimumNArgs(1), - RunE: migrate, - } - ErrBadContextValue = errors.New("context value is empty or has wrong type") -) +// MigrateCmd is a github.com/pressly/goose database migrate wrapper command. +var MigrateCmd = &cobra.Command{ + Use: "migrate", + Short: "Database migrations command", + ValidArgs: []string{"up", "up-by-one", "up-to", "create", "down", "down-to", "fix", "redo", "reset", "status", "version"}, + Args: cobra.MinimumNArgs(1), + RunE: migrate, +} -// MigrateUsage shows command usage. -// Add it to MigrateCmd like MigrateCmd.SetUsageFunc(MigrateUsage). -func MigrateUsage(cmd *cobra.Command) error { +// migrateUsage shows command usage. +func migrateUsage(cmd *cobra.Command) error { w := cmd.OutOrStderr() if _, err := w.Write([]byte(fmt.Sprintf(`Usage: %s %s [args] @@ -54,7 +49,7 @@ Args: status prints the status of all migrations version prints the current version of the database `, cmd.Parent().Name(), cmd.Name()))); err != nil { - return fmt.Errorf("MigrateUsage err: %w", err) + return fmt.Errorf("migrateUsage err: %w", err) } return nil @@ -66,15 +61,17 @@ func migrate(cmd *cobra.Command, args []string) error { appStage, dbCfg, logCfg, appInfo, err := getConfigs(cmd.Context()) if err != nil { - return fmt.Errorf(errStrFormat, method, "getConfig", err) + return fmt.Errorf(CmdErrStrFormat, method, "getConfig", err) } if err := log.Init(appStage.String(), logCfg, appInfo.GetAlias(), appInfo.GetVersion(), os.Stdout); err != nil { log.Error().Err(err).Send() - return fmt.Errorf(errStrFormat, method, "log.Init", err) + return fmt.Errorf(CmdErrStrFormat, method, "log.Init", err) } + log.Info().Msg(appInfo.Summary()) + command := args[0] log.Debug().Str("command", command).Strs("command args", args[0:]).Msg("run migrate command") @@ -83,7 +80,7 @@ func migrate(cmd *cobra.Command, args []string) error { if err != nil { log.Error().Err(err).Str("dsn", dbCfg.GetMigrationDSN()).Msg("fail to parse config") - return fmt.Errorf(errStrFormat, method, "ParseConfig", err) + return fmt.Errorf(CmdErrStrFormat, method, "ParseConfig", err) } cfg.Logger = zerologadapter.NewLogger(log.Logger().With().CallerWithSkipFrameCount(4).Logger()) // nolint:gomnd @@ -110,7 +107,7 @@ func migrate(cmd *cobra.Command, args []string) error { if err := db.Close(); err != nil { log.Error().Str("dsn", dbCfg.GetDSN()).Err(err).Msg("fail to close DB connection") - os.Exit(failureCode) + os.Exit(CmdFailureCode) } }() @@ -158,8 +155,7 @@ INSERT INTO %s.%s ("version_id", "is_applied", "tstamp") VALUES ('0', 't', NOW() if t == nil { log.Trace().Msg("goose table doesn't exists. let's create it") - _, err := db.Exec(create) - if err != nil { + if _, err := db.Exec(create); err != nil { return fmt.Errorf("checkInit db.Exec error: %w", err) } diff --git a/seed.go b/seed.go new file mode 100644 index 0000000..de498ae --- /dev/null +++ b/seed.go @@ -0,0 +1,127 @@ +package commands + +import ( + "context" + "fmt" + "strings" + + log "github.com/spacetab-io/logs-go/v2" + "github.com/spf13/cobra" +) + +type SeedsInterface interface { + GetMethods() map[string]SeedInterface + GetMethod(name string) (SeedInterface, error) + SeedsList() []string +} + +type SeedInterface interface { + Enabled() bool + Name() string + Seed() error +} + +// SeedCmd is a database seeding wrapper command. +var ( + SeedCmd = &cobra.Command{ + Use: "seed", + Short: "Database seeding command", + ValidArgs: []string{"run", "run-all", "list"}, + Args: cobra.MinimumNArgs(1), + } + seedListCmd = &cobra.Command{ + Use: "list", + RunE: seedList, + } + seedRunAllCmd = &cobra.Command{ + Use: "run-all", + RunE: seedRunAll, + } + seedRunCmd = &cobra.Command{ + Use: "run", + Args: cobra.MinimumNArgs(1), + RunE: seedRun, + } +) + +// seedUsage shows seed command usage. +// Add it to SeedCmd like SeedCmd.SetUsageFunc(seedUsage). +func seedUsage(cmd *cobra.Command) error { + w := cmd.OutOrStderr() + if _, err := w.Write([]byte(fmt.Sprintf(`Usage: + %s %s [args] + +Args: + run runs concreete seed + run-all applies all seeds + list shows available seeds list +`, cmd.Parent().Name(), cmd.Name()))); err != nil { + return fmt.Errorf("seedUsage err: %w", err) + } + + return nil +} + +func seedList(cmd *cobra.Command, _ []string) error { + s, err := getAppSeeder(cmd.Context()) + if err != nil { + return err + } + + cmd.Printf("Available seed list:\n %s\n", strings.Join(s.SeedsList(), "\n ")) + + return nil +} + +func seedRun(cmd *cobra.Command, args []string) error { + s, err := getAppSeeder(cmd.Context()) + if err != nil { + return fmt.Errorf("seedRun getAppSeeder() error: %w", err) + } + + log.Trace().Strs("seeds", args).Msg("Running seeder...") + + // Execute only the given method names + for _, item := range args { + seed, err := s.GetMethod(item) + if err != nil { + return fmt.Errorf("seedRun GetMethod error: %w", err) + } + + if err := seed.Seed(); err != nil { + return fmt.Errorf("seedRun seed.Seed error: %w", err) + } + } + + return nil +} + +// Execute all seeders if no method name is given. +func seedRunAll(cmd *cobra.Command, _ []string) error { + s, err := getAppSeeder(cmd.Context()) + if err != nil { + return fmt.Errorf("seedRunAll getAppSeeder() error: %w", err) + } + + log.Trace().Msg("Running all seeder...") + + // We are looping over the method on a Seeder struct + for _, seed := range s.GetMethods() { + // Get the method in the current iteration + // Execute seeder + if err := seed.Seed(); err != nil { + return fmt.Errorf("seedRunAll seed.Seed() error: %w", err) + } + } + + return nil +} + +func getAppSeeder(ctx context.Context) (SeedsInterface, error) { + s, ok := ctx.Value(CommandContextObjectKeySeeder).(SeedsInterface) + if !ok { + return nil, fmt.Errorf("%w: app seed (cfg.appInfo)", ErrBadContextValue) + } + + return s, nil +}