From e057e5b10b63436db3a52988714b72782f035685 Mon Sep 17 00:00:00 2001 From: Miguel de la Cruz Date: Wed, 16 Dec 2020 20:45:17 +0100 Subject: [PATCH] [MM-31085] Create a db init command to initialize the database (#16489) Automatic Merge --- cmd/mattermost/commands/db.go | 72 +++++++++++++++++++++++++++++++ cmd/mattermost/commands/server.go | 25 ----------- cmd/mattermost/commands/utils.go | 26 +++++++++++ config/store.go | 3 +- config/utils.go | 4 ++ config/utils_test.go | 45 +++++++++++++++++++ 6 files changed, 148 insertions(+), 27 deletions(-) create mode 100644 cmd/mattermost/commands/db.go diff --git a/cmd/mattermost/commands/db.go b/cmd/mattermost/commands/db.go new file mode 100644 index 0000000000..5a3685b213 --- /dev/null +++ b/cmd/mattermost/commands/db.go @@ -0,0 +1,72 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package commands + +import ( + "fmt" + + "github.com/mattermost/mattermost-server/v5/config" + "github.com/mattermost/mattermost-server/v5/store/sqlstore" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var DbCmd = &cobra.Command{ + Use: "db", + Short: "Commands related to the database", +} + +var InitDbCmd = &cobra.Command{ + Use: "init", + Short: "Initialize the database", + Long: `Initialize the database for a given DSN, executing the migrations and loading the custom defaults if any. + +This command should be run using a database configuration DSN.`, + Example: ` # you can use the config flag to pass the DSN + $ mattermost db init --config postgres://localhost/mattermost + + # or you can use the MM_CONFIG environment variable + $ MM_CONFIG=postgres://localhost/mattermost mattermost db init + + # and you can set a custom defaults file to be loaded into the database + $ MM_CUSTOM_DEFAULTS_PATH=custom.json MM_CONFIG=postgres://localhost/mattermost mattermost db init`, + Args: cobra.NoArgs, + RunE: initDbCmdF, +} + +func init() { + DbCmd.AddCommand( + InitDbCmd, + ) + + RootCmd.AddCommand( + DbCmd, + ) +} + +func initDbCmdF(command *cobra.Command, _ []string) error { + dsn := getConfigDSN(command, config.GetEnvironment()) + if !config.IsDatabaseDSN(dsn) { + return errors.New("this command should be run using a database configuration DSN") + } + + customDefaults, err := loadCustomDefaults() + if err != nil { + return errors.Wrap(err, "error loading custom configuration defaults") + } + + configStore, err := config.NewStore(getConfigDSN(command, config.GetEnvironment()), false, customDefaults) + if err != nil { + return errors.Wrap(err, "failed to load configuration") + } + defer configStore.Close() + + sqlStore := sqlstore.New(configStore.Get().SqlSettings, nil) + defer sqlStore.Close() + + fmt.Println("Database store correctly initialised") + + return nil +} diff --git a/cmd/mattermost/commands/server.go b/cmd/mattermost/commands/server.go index ac988ef5fd..a30e2a8de2 100644 --- a/cmd/mattermost/commands/server.go +++ b/cmd/mattermost/commands/server.go @@ -4,7 +4,6 @@ package commands import ( - "encoding/json" "net" "os" "os/signal" @@ -16,7 +15,6 @@ import ( "github.com/mattermost/mattermost-server/v5/config" "github.com/mattermost/mattermost-server/v5/manualtesting" "github.com/mattermost/mattermost-server/v5/mlog" - "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/utils" "github.com/mattermost/mattermost-server/v5/web" "github.com/mattermost/mattermost-server/v5/wsapi" @@ -24,8 +22,6 @@ import ( "github.com/spf13/cobra" ) -const CUSTOM_DEFAULTS_ENV_VAR = "MM_CUSTOM_DEFAULTS_PATH" - var serverCmd = &cobra.Command{ Use: "server", Short: "Run the Mattermost server", @@ -38,27 +34,6 @@ func init() { RootCmd.RunE = serverCmdF } -func loadCustomDefaults() (*model.Config, error) { - customDefaultsPath := os.Getenv(CUSTOM_DEFAULTS_ENV_VAR) - if customDefaultsPath == "" { - return nil, nil - } - - file, err := os.Open(customDefaultsPath) - if err != nil { - return nil, errors.Wrapf(err, "unable to open custom defaults file at %q", customDefaultsPath) - } - defer file.Close() - - var customDefaults *model.Config - err = json.NewDecoder(file).Decode(&customDefaults) - if err != nil { - return nil, errors.Wrap(err, "unable to decode custom defaults configuration") - } - - return customDefaults, nil -} - func serverCmdF(command *cobra.Command, args []string) error { disableConfigWatch, _ := command.Flags().GetBool("disableconfigwatch") usedPlatform, _ := command.Flags().GetBool("platform") diff --git a/cmd/mattermost/commands/utils.go b/cmd/mattermost/commands/utils.go index c5d3e8bdf1..38012c80a9 100644 --- a/cmd/mattermost/commands/utils.go +++ b/cmd/mattermost/commands/utils.go @@ -5,15 +5,20 @@ package commands import ( "bytes" + "encoding/json" "fmt" + "os" "reflect" "sort" "strings" "github.com/mattermost/mattermost-server/v5/mlog" + "github.com/mattermost/mattermost-server/v5/model" "github.com/spf13/cobra" ) +const CUSTOM_DEFAULTS_ENV_VAR = "MM_CUSTOM_DEFAULTS_PATH" + // prettyPrintStruct will return a prettyPrint version of a given struct func prettyPrintStruct(t interface{}) string { return prettyPrintMap(structToMap(t)) @@ -116,3 +121,24 @@ func getConfigDSN(command *cobra.Command, env map[string]string) string { return configDSN } + +func loadCustomDefaults() (*model.Config, error) { + customDefaultsPath := os.Getenv(CUSTOM_DEFAULTS_ENV_VAR) + if customDefaultsPath == "" { + return nil, nil + } + + file, err := os.Open(customDefaultsPath) + if err != nil { + return nil, fmt.Errorf("unable to open custom defaults file at %q: %w", customDefaultsPath, err) + } + defer file.Close() + + var customDefaults *model.Config + err = json.NewDecoder(file).Decode(&customDefaults) + if err != nil { + return nil, fmt.Errorf("unable to decode custom defaults configuration: %w", err) + } + + return customDefaults, nil +} diff --git a/config/store.go b/config/store.go index 709d7057ea..7f8de4906d 100644 --- a/config/store.go +++ b/config/store.go @@ -6,7 +6,6 @@ package config import ( "bytes" "encoding/json" - "strings" "sync" "github.com/mattermost/mattermost-server/v5/model" @@ -83,7 +82,7 @@ func NewStoreFromBacking(backingStore BackingStore, customDefaults *model.Config } func getBackingStore(dsn string, watch bool) (BackingStore, error) { - if strings.HasPrefix(dsn, "mysql://") || strings.HasPrefix(dsn, "postgres://") { + if IsDatabaseDSN(dsn) { return NewDatabaseStore(dsn) } diff --git a/config/utils.go b/config/utils.go index 4bbaa1fdda..5424802a00 100644 --- a/config/utils.go +++ b/config/utils.go @@ -173,6 +173,10 @@ func Merge(cfg *model.Config, patch *model.Config, mergeConfig *utils.MergeConfi return &retCfg, nil } +func IsDatabaseDSN(dsn string) bool { + return strings.HasPrefix(dsn, "mysql://") || strings.HasPrefix(dsn, "postgres://") +} + // stripPassword remove the password from a given DSN func stripPassword(dsn, schema string) string { prefix := schema + "://" diff --git a/config/utils_test.go b/config/utils_test.go index 03393cad5c..753319f561 100644 --- a/config/utils_test.go +++ b/config/utils_test.go @@ -145,6 +145,51 @@ func TestFixInvalidLocales(t *testing.T) { assert.Contains(t, *cfg.LocalizationSettings.AvailableLocales, *cfg.LocalizationSettings.DefaultClientLocale, "DefaultClientLocale should have been added to AvailableLocales") } +func TestIsDatabaseDSN(t *testing.T) { + testCases := []struct { + Name string + DSN string + Expected bool + }{ + { + Name: "Mysql DSN", + DSN: "mysql://localhost", + Expected: true, + }, + { + Name: "Mysql DSN", + DSN: "mysql://localhost", + Expected: true, + }, + { + Name: "Empty DSN", + DSN: "", + Expected: false, + }, + { + Name: "Default file DSN", + DSN: "config.json", + Expected: false, + }, + { + Name: "Relative path DSN", + DSN: "configuration/config.json", + Expected: false, + }, + { + Name: "Absolute path DSN", + DSN: "/opt/mattermost/configuration/config.json", + Expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + assert.Equal(t, tc.Expected, IsDatabaseDSN(tc.DSN)) + }) + } +} + func TestStripPassword(t *testing.T) { for name, test := range map[string]struct { DSN string